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


Сокращение задержки с помощью цепочек буферов DXGI 1.3

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

Как ожидание обратного буфера уменьшает задержку?

При использовании цепочки буферов модели переверки буфер обратно буфера буфер "перевернуты" помещаются в очередь всякий раз, когда игра вызывает IDXGISwapChain::P resent. Когда цикл отрисовки вызывает Present(), система блокирует поток до тех пор, пока не будет выполнено представление предыдущего кадра, что делает место для очереди нового кадра, прежде чем он фактически представляет. Это приводит к дополнительной задержке между временем, когда игра рисует кадр и время, когда система позволяет отображать этот кадр. Во многих случаях система достигнет стабильного равновесия, где игра всегда ждет почти полный дополнительный кадр между временем отрисовки и временем, когда он представляет каждый кадр. Лучше ждать, пока система будет готова принять новый кадр, а затем отрисовка кадра на основе текущих данных и очередь кадра немедленно.

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

Шаг 1. Создание цепочки подождите буферов

При вызове CreateSwapChainForCoreWindow укажите флаг DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT.

swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // Enable GetFrameLatencyWaitableObject().

Примечание.

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

// If the swap chain already exists, resize it.
HRESULT hr = m_swapChain->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_FRAME_LATENCY_WAITABLE_OBJECT // Enable GetFrameLatencyWaitableObject().
    );

Шаг 2. Настройка задержки кадра

Задайте задержку кадра с помощью API IDXGISwapChain2::SetMaximumFrameLatency вместо вызова IDXGIDevice1::SetMaximumFrameLatency.

По умолчанию задержка кадров для ожидающих цепочек буферов имеет значение 1, что приводит к минимальной задержке, но также снижает параллелизм ЦП-GPU. Если требуется увеличить параллелизм ЦП-GPU для достижения 60 FPS - то есть, если ЦП и GPU каждый тратит менее 16,7 мс на отрисовку кадров, но их объединенная сумма больше 16,7 мс — установите задержку кадра на 2. Это позволяет GPU обрабатывать рабочие процессы в очереди ЦП во время предыдущего кадра, одновременно позволяя ЦП отправлять команды отрисовки для текущего кадра независимо.

// Swapchains created with the DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT flag use their
// own per-swapchain latency setting instead of the one associated with the DXGI device. The
// default per-swapchain latency is 1, which ensures that DXGI does not queue more than one frame
// at a time. This both reduces latency and ensures that the application will only render after
// each VSync, minimizing power consumption.
//DX::ThrowIfFailed(
//    swapChain2->SetMaximumFrameLatency(1)
//    );

Шаг 3. Получение ожидаемого объекта из цепочки буферов

Вызовите IDXGISwapChain2::GetFrameLatencyWaitableObject , чтобы получить дескриптор ожидания. Дескриптор ожидания — это указатель на ожидающий объект. Сохраните этот дескриптор для использования циклом отрисовки.

// Get the frame latency waitable object, which is used by the WaitOnSwapChain method. This
// requires that swap chain be created with the DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT
// flag.
m_frameLatencyWaitableObject = swapChain2->GetFrameLatencyWaitableObject();

Шаг 4. Подождите перед отрисовкой каждого кадра

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

В следующем примере показан цикл отрисовки из примера DirectXLatency:

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        // Block this thread until the swap chain is finished presenting. Note that it is
        // important to call this before the first Present in order to minimize the latency
        // of the swap chain.
        m_deviceResources->WaitOnSwapChain();

        // Process any UI events in the queue.
        CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        // Update app state in response to any UI events that occurred.
        m_main->Update();

        // Render the scene.
        m_main->Render();

        // Present the scene.
        m_deviceResources->Present();
    }
    else
    {
        // The window is hidden. Block until a UI event occurs.
        CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

В следующем примере показан вызов WaitForSingleObjectEx из примера DirectXLatency:

// Block the current thread until the swap chain has finished presenting.
void DX::DeviceResources::WaitOnSwapChain()
{
    DWORD result = WaitForSingleObjectEx(
        m_frameLatencyWaitableObject,
        1000, // 1 second timeout (shouldn't ever occur)
        true
        );
}

Что должно делать моя игра, пока она ожидает, пока цепочка буферов будет присутствовать?

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

  • Обработка сетевых событий
  • Обновление ИИ
  • Физика на основе ЦП
  • Отложенная отложенная отрисовка контекста (на поддерживаемых устройствах)
  • Загрузка ресурсов

Дополнительные сведения о многопоточных программированиях в Windows см. в следующих связанных разделах.