Поделиться через


Масштабирование цепочки буферов и наложения

Узнайте, как создавать масштабируемые цепочки буферов для ускорения отрисовки на мобильных устройствах и использовать цепочки переключения наложения (когда они доступны) для повышения качества визуального элемента.

Цепочки буферов в DirectX 11.2

Direct3D 11.2 позволяет создавать приложения универсальная платформа Windows (UWP) с цепочками буферов, масштабируемыми из неродных (сокращенных) разрешений, что позволяет ускорить скорость заполнения. Direct3D 11.2 также включает API для отрисовки с аппаратными наложениями, чтобы вы могли представить пользовательский интерфейс в другой цепочке буферов при собственном разрешении. Это позволяет игре рисовать пользовательский интерфейс в полном собственном разрешении при сохранении высокой частоты кадров, тем самым обеспечивая оптимальное использование мобильных устройств и дисплеев с высоким уровнем DPI (например, 3840 к 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, преобразование поворота необходимо изменить, чтобы компенсировать исходный регион.

Создание цепочки буферов аппаратного наложения для элементов пользовательского интерфейса

При использовании масштабирования цепочки буферов существует присущий недостаток в том, что пользовательский интерфейс также масштабируется, что потенциально делает его размытым и трудным для использования. На устройствах с аппаратной поддержкой цепочек переключения эта проблема полностью устраняется путем отрисовки пользовательского интерфейса при собственном разрешении в цепочке буферов, отдельной от содержимого игры в режиме реального времени. Обратите внимание, что этот метод применяется только к цепочкам буферов CoreWindow . Его нельзя использовать с взаимодействием XAML.

Выполните следующие действия, чтобы создать цепочку буферов переднего плана, которая использует возможность наложения оборудования. Эти действия выполняются после первого создания цепочки буферов для содержимого игры в режиме реального времени, как описано выше.

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

    Адаптер DXGI поддерживает наложения, если выходной адаптер возвращает значение True для SupportsOverlays.

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

    Примечание. Если адаптер DXGI поддерживает наложения, перейдите к следующему шагу. Если устройство не поддерживает наложения, отрисовка с несколькими цепочками буферов не будет эффективной. Вместо этого отрисуйте пользовательский интерфейс при сниженном разрешении в той же цепочке буферов, что и содержимое игры в режиме реального времени.

     

  2. Создайте цепочку буферов переднего плана с помощью IDXGIFactory2::CreateSwapChainForCoreWindow. Следующие параметры должны быть заданы в DXGI_SWAP_CHAIN_DESC1 , предоставленном параметру pDesc :

     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. Цепочки буферов переднего плана всегда используют предварительно подготовленные альфа-каналы. Ожидается, что значения цвета каждого пикселя уже умножаются на альфа-значение перед представлением кадра. Например, 100% белый пиксель BGRA на 50 % альфа имеет значение (0,5, 0,5, 0,5, 0,5, 0,5).

    Шаг альфа-предварительной подготовки можно выполнить на этапе слияния выходных данных, применив состояние смешения приложения (см. id3D11BlendState) D3D11_RENDER_TARGET_BLEND_DESC с полем SrcBlend структуры D3D11_SRC_ALPHA. Также можно использовать ресурсы с предварительно умноженными альфа-значениями.

    Если шаг альфа-премульпликации не выполнен, цвета в цепочке буферов переднего плана будут более яркими, чем ожидалось.

  5. В зависимости от того, была ли создана цепочка переключения переднего плана, область рисования 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);
        }
    }