Windows 10窗口模式下等待VBLANK提交画面的正确方式
嘿,针对Windows 10窗口模式下等待VBLANK提交画面的问题,我来给你拆解下正确的姿势,顺便解决你遇到的卡顿问题——你的推测其实已经摸到了核心原因,咱们一步步来理清楚。
卡顿原因的验证
你说的没错:DWM(桌面窗口管理器)会在VBLANK事件到来前完成所有窗口画面的合成工作,然后在VBLANK的时间间隔内,快速把合成好的帧写入前缓冲区。如果你的Present调用时机刚好落在DWM已经完成写入之后,那当前帧就只能等到下一个VBLANK周期才能显示,这就会凭空多出来一帧的延迟,表现为偶尔的卡顿掉帧。
正确的同步方式:利用DXGI与DWM协同
在窗口模式下,直接去监听硬件VBLANK是不靠谱的——因为DWM的合成节奏可能和硬件垂直同步不完全绑定,而且它会接管最终的画面输出。正确的做法是使用DXGI提供的帧延迟可等待对象,让你的渲染逻辑和DWM的合成节奏对齐:
步骤1:创建交换链时启用帧延迟可等待对象
创建DXGI交换链的时候,一定要加上DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT标志,这样交换链会生成一个可等待的同步句柄,用来和DWM的帧节奏同步:
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; // 填充交换链的基础参数:比如缓冲区格式、大小、缓冲区数量等 swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.Width = windowWidth; swapChainDesc.Height = windowHeight; swapChainDesc.BufferCount = 2; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapChainDesc.SampleDesc.Count = 1; // 关键:启用帧延迟可等待对象 swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; // 创建交换链并获取IDXGISwapChain3接口(需要DXGI 1.3及以上) ComPtr<IDXGISwapChain3> swapChain; HRESULT hr = dxgiFactory->CreateSwapChainForHwnd( device.Get(), hwnd, &swapChainDesc, nullptr, nullptr, reinterpret_cast<IDXGISwapChain**>(swapChain.GetAddressOf()) );
步骤2:设置最大帧延迟为1
为了避免交换链积累过多待提交的帧(这也是卡顿的常见诱因),通过IDXGISwapChain3::SetMaximumFrameLatency把最大帧延迟设为1,确保每次只等待当前帧的同步:
swapChain->SetMaximumFrameLatency(1);
步骤3:渲染循环中等待同步对象再提交
在每次执行渲染命令、调用Present之前,先等待交换链的可等待对象,这样就能保证你的Present调用刚好卡在DWM准备好接收新帧的时机,不会错过VBLANK窗口:
// 渲染循环 while (isRunning) { // 1. 等待帧同步对象,对齐DWM的节奏 HANDLE frameSyncHandle = swapChain->GetFrameLatencyWaitableObject(); WaitForSingleObject(frameSyncHandle, INFINITE); // 2. 执行渲染逻辑:清屏、绘制场景、执行命令列表等 // ... // 3. 提交帧到交换链 swapChain->Present(1, 0); // 第一个参数1表示启用垂直同步(配合DXGI的同步更稳妥) }
额外注意事项
- 不要尝试自己用
QueryPerformanceCounter或者其他硬件计时器去监听VBLANK,窗口模式下DWM的合成逻辑会让这种方法失效,反而容易导致更严重的同步问题。 - 如果你的项目使用的是较旧的DXGI版本(低于1.3),可以尝试用
DXGI_PRESENT_TEST标志来测试Present是否能立即提交,但这种方法的可靠性远不如上面的可等待对象方案,建议尽量升级到DXGI 1.3及以上。 - 确保你的渲染线程和UI线程没有互相阻塞,比如不要在渲染线程里做UI操作,也不要在UI线程里等待渲染完成,避免额外的卡顿。
内容的提问来源于stack exchange,提问作者cairnc
相关产品推荐
相关产品推荐

