使用 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. 设置帧延迟
使用 IDXGISwapChain2::SetMaximumFrameLatency API 设置帧延迟,而不是调用 IDXGIDevice1::SetMaximumFrameLatency。
默认情况下,可等待交换链的帧延迟设置为 1,这会导致最短的延迟,但也减少了 CPU-GPU 并行度。 如果需要增加 CPU-GPU 并行度来实现 60 FPS-也就是说,如果 CPU 和 GPU 每次花费不到 16.7 毫秒的帧处理渲染工作,但其组合总和大于 16.7 毫秒 - 将帧延迟设置为 2。 这样,GPU 可以处理上一帧中由 CPU 排队的任务,同时允许 CPU 独立提交当前帧的渲染命令。
// 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. 在呈现每个帧之前等待
在开始呈现每个帧之前,呈现循环应等待交换链通过可等待对象发出信号。 这包括使用交换链呈现的第一个帧。 使用在步骤 2 中检索到的等待句柄,通过 WaitForSingleObjectEx来发出每个帧开始的信号。
以下示例演示了 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);
}
}
以下示例演示 DirectXLatency 示例中的 WaitForSingleObjectEx 调用:
// 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
);
}
在等待交换链呈现的过程中,我的游戏应该做什么?
如果你的游戏中没有任何任务会阻塞渲染循环,那么让它等待交换链的呈现可能是有利的,因为这能节省电量,这对于移动设备尤其重要。 否则,可以在游戏等待交换链呈现时使用多线程来完成工作。 以下是游戏可以完成的几个任务:
- 处理网络事件
- 更新 AI 系统
- 基于 CPU 的物理
- 延迟上下文呈现(在受支持的设备上)
- 资产加载
有关 Windows 中多线程编程的详细信息,请参阅以下相关主题。