You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DirectX12 CPU/GPU同步:微软与英伟达示例的差异及优劣解析

关于DirectX12 CPU/GPU同步机制的两种帧同步实现解析

在理解DirectX12的CPU/GPU同步机制时,针对帧缓冲同步的两种典型实现存在困惑,下面分别解析微软HelloFrameBuffering示例与英伟达示例的逻辑设计及优缺点。

微软HelloFrameBuffering实现

// Prepare to render the next frame.
void D3D12HelloFrameBuffering::MoveToNextFrame()
{
    // Schedule a Signal command in the queue.
    const UINT64 currentFenceValue = m_fenceValues[m_frameIndex];
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));

    // Update the frame index.
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();

    // If the next frame is not ready to be rendered yet, wait until it is ready.
    if (m_fence->GetCompletedValue() < m_fenceValues[m_frameIndex])
    {
        ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_frameIndex], m_fenceEvent));
        WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
    }

    // Set the fence value for the next frame.
    m_fenceValues[m_frameIndex] = currentFenceValue + 1;
}

该实现的核心是复用全局单个Fence,为每个帧缓冲维护独立的Fence值:

  • 第一步用当前帧索引对应的Fence值向命令队列发送Signal,标记当前帧的GPU任务完成后需触发的Fence状态。
  • 紧接着更新帧索引到下一个待使用的缓冲,此时等待的是下一个缓冲对应的Fence值——这是因为下一个缓冲可能还在被GPU占用,必须等它的上一轮GPU任务完成(Fence值被触发),CPU才能安全复用该缓冲的资源。
  • 最后给下一个缓冲的Fence值递增,为下一次循环做准备。

这种设计的本质是:当前帧的Signal标记的是上一轮该缓冲的GPU任务结束,而等待的是下一个缓冲的上一轮任务结束,确保CPU不会在GPU仍在使用缓冲时覆盖数据。

英伟达示例实现

struct FrameContext
{
    ComPtr<ID3D12CommandAllocator> m_allocator;
    ComPtr<ID3D12CommandAllocator> m_computeAllocator;
    ComPtr<ID3D12Fence>            m_fence;
    uint64_t                       m_fenceValue = 0;
};

void DeviceResources::MoveToNextFrame()
{
    FrameContext* ctx = &m_frameContext[m_frameIndex];
    DX::ThrowIfFailed(m_commandQueue->Signal(ctx->m_fence.Get(), ctx->m_fenceValue));
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
    if (ctx->m_fence->GetCompletedValue() < ctx->m_fenceValue)
    {
        DX::ThrowIfFailed(ctx->m_fence->SetEventOnCompletion(ctx->m_fenceValue, m_fenceEvent.Get()));
        WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, false);
    }
    ctx->m_fenceValue++;
}

该实现采用每个帧缓冲绑定独立Fence实例的设计,每个FrameContext包含专属的Fence和对应的Fence值:

  • 第一步用当前帧上下文的Fence和Fence值发送Signal,标记当前帧的GPU任务完成条件。
  • 更新帧索引后,等待的是当前帧上下文的Fence值——也就是刚刚发送Signal的那个Fence,确保当前帧的GPU任务完成后,CPU才能复用该上下文的资源(比如命令分配器)。
  • 最后递增当前帧上下文的Fence值,为下一次使用该缓冲时做准备。

两种实现的优缺点对比

微软实现(全局单Fence+多Fence值)

  • 优点:
    • 资源占用低:仅需创建一个Fence实例,节省GPU/CPU资源,适合轻量级场景。
    • 状态集中:所有帧的Fence状态统一存储在数组中,便于集中管理。
  • 缺点:
    • 理解成本高:帧索引更新与Fence值的对应关系较为绕,新手容易混淆。
    • 扩展性弱:若需为不同类型队列(如图形、计算)做同步,单Fence易出现冲突,需额外区分逻辑。

英伟达实现(每个帧缓冲独立Fence)

  • 优点:
    • 逻辑清晰:每个帧的同步状态完全独立,Signal与Wait的对应关系一目了然,新手易理解。
    • 扩展性强:可为每个帧上下文的不同队列分配独立Fence,实现精细化同步不同类型GPU任务。
    • 隔离性好:单个帧的同步问题不会影响其他帧,调试更便捷。
  • 缺点:
    • 资源占用略高:每个帧缓冲都需创建独立Fence实例,帧缓冲数量较多时会增加少量资源开销,但现代硬件下可忽略不计。

内容的提问来源于stack exchange,提问作者ThIsJaCk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:14:54