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

DirectX 12多交换链场景下VSync的正确处理方案问询

DirectX 12多窗口VSync优化方案

问题背景

开发基于DirectX 12的多窗口合作游戏,每个玩家对应独立窗口(部署在多显示器上防止窥屏作弊),但调用交换链Present时遇到VSync相关的严重掉帧问题。当前代码通过关闭VSync(syncInterval=0)规避问题,但需要实现正确的VSync同步逻辑,同时避免帧率暴跌。

核心问题分析

开启VSync(syncInterval=1)时,循环调用每个交换链的Present会触发多次阻塞等待垂直同步:每个Present(syncInterval=1)都会阻塞到当前显示器的下一次垂直刷新,4个窗口就会触发4次等待,最终帧率被拉低到显示器刷新率的1/4左右。此外,多个交换链共用同一命令队列时,渲染与Present的同步逻辑不合理也会加剧性能损耗。

解决方案

  • 统一垂直同步等待,避免重复阻塞:只在第一个交换链Present时等待垂直同步,后续交换链直接提交而不重复等待。DXGI特性支持:当多个交换链共享同一命令队列且渲染工作已同步完成时,第一个Present(syncInterval=1)会等待到垂直刷新,后续Present(syncInterval=0)可直接完成,不会额外阻塞。
  • 绑定渲染与垂直同步周期:使用D3D12的Fence机制,将所有玩家的渲染命令执行与垂直同步周期绑定,确保所有渲染工作在垂直刷新前完成,避免Present时出现无意义的等待延迟。
  • 适配多显示器刷新率差异:若窗口分布在不同刷新率的显示器上,需为每个显示器单独处理VSync——针对每个显示器创建独立的同步Fence,等待对应显示器的垂直刷新信号后,再执行该显示器上窗口的Present。

代码修改示例

调整Present逻辑(基础优化)

while(bRunning)
{
    // ... 执行所有玩家的渲染命令
    sRENDERER.gfxCommandQueue->ExecuteCommandLists(dwNumPlayers, sRENDERER.gfxCommandList[dwFrame]);
    SignalCurrentFrame();

    uint32_t syncInterval = 1;
    uint32_t presentFlags = 0;

    // 第一个窗口执行带VSync的Present,触发一次垂直同步等待
    if (FAILED(swapChain[0]->Present(syncInterval, presentFlags)))
    {
        logError("Error presenting swap chain buffer!\n");
        break;
    }

    // 后续窗口跳过VSync等待,直接提交
    syncInterval = 0;
    for(uint32_t dwPlayer = 1; dwPlayer < dwNumPlayers; ++dwPlayer)
    {
        if (FAILED(swapChain[dwPlayer]->Present(syncInterval, presentFlags)))
        {
            logError("Error presenting swap chain buffer!\n");
            break;
        }
    }
}

结合Fence同步渲染周期(精准控制)

// 初始化时创建Fence和事件
ID3D12Fence* pFence = nullptr;
HANDLE hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
UINT64 fenceValue = 0;

// 初始化交换链时添加帧延迟控制
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING | DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
// ... 创建交换链后设置最大帧延迟
swapChain[dwPlayer]->SetMaximumFrameLatency(1);

while(bRunning)
{
    // ... 执行所有玩家的渲染命令
    sRENDERER.gfxCommandQueue->ExecuteCommandLists(dwNumPlayers, sRENDERER.gfxCommandList[dwFrame]);
    
    // 标记命令队列完成点并等待执行完成
    UINT64 currentFenceValue = ++fenceValue;
    sRENDERER.gfxCommandQueue->Signal(pFence, currentFenceValue);
    if(pFence->GetCompletedValue() < currentFenceValue)
    {
        pFence->SetEventOnCompletion(currentFenceValue, hFenceEvent);
        WaitForSingleObject(hFenceEvent, INFINITE);
    }

    // 批量执行Present,无需重复等待VSync
    uint32_t syncInterval = 0;
    uint32_t presentFlags = 0;
    for(uint32_t dwPlayer = 0; dwPlayer < dwNumPlayers; ++dwPlayer)
    {
        if (FAILED(swapChain[dwPlayer]->Present(syncInterval, presentFlags)))
        {
            logError("Error presenting swap chain buffer!\n");
            break;
        }
    }
}

关键注意事项

  • 若窗口分布在不同显示器,必须为每个显示器单独处理VSync,避免跨显示器同步导致的帧率异常。
  • 移除不必要的窗口样式:如CS_HREDRAW | CS_VREDRAW会触发额外的窗口重绘开销,若游戏自身处理渲染逻辑可直接移除。
  • 确保所有交换链共享同一个DXGI Adapter,避免跨Adapter同步带来的性能损耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 02:12:41