如何在每个垂直同步中完成一个渲染(不重复,不跳过)?

时间:2012-05-08 13:38:08

标签: opengl directx direct3d direct2d vsync

我正在尝试进行垂直同步渲染,以便每个垂直同步完成一次渲染,而不跳过或重复任何帧。我需要这个在Windows 7和(将来)Windows 8下工作。

它基本上包括绘制适合屏幕的QUADS序列,以便原始图像中的像素与屏幕上的1:1像素匹配。使用OpenGL或DirectX,渲染部分不是问题。问题是正确的同步。

我之前尝试过使用带有WGL_EXT_swap_control扩展名的OpenGL,通过绘图然后调用

SwapBuffers(g_hDC);
glFinish();

我尝试了这两个指令的所有组合和排列以及glFlush(),并且它不可靠。

然后我尝试使用Direct3D 10,通过绘图然后调用

g_pSwapChain->Present(1, 0);
pOutput->WaitForVBlank();

其中g_pSwapChainIDXGISwapChain*pOutput是与该SwapChain关联的IDXGIOutput*

两个版本,OpenGL和Direct3D都会产生相同的结果:第一个序列,例如60帧,并不能达到应有的水平(而不是大约1000毫秒,60hz,持续时间等于1030或1050毫秒),以下似乎工作正常(约1000.40ms),但不时似乎跳过一帧。我用QueryPerformanceCounter进行测量。

在Direct3D上,尝试仅使用WaitForVBlank的循环,1000次迭代的持续时间始终为1000.40,几乎没有变化。

所以这里的麻烦是不确切知道每个函数何时返回,以及交换是否在垂直同步期间完成(不是更早,以避免撕裂)。

理想情况下(如果我没有记错的话),为了实现我想要的,它将执行一次渲染,等到同步开始,在同步期间交换,然后等到同步完成。如何使用OpenGL或DirectX做到这一点?

修改WaitForVSync 60x的测试循环始终从1000.30ms到1000.50ms。 在Present(1,0)之前使用WaitForVSync的相同循环,没有其他任何内容,没有渲染,需要相同的时间,但有时会失败并需要1017毫秒,就像重复一帧一样。没有渲染,所以这里有问题。

6 个答案:

答案 0 :(得分:3)

我在DX11中遇到同样的问题。我想保证我的帧渲染代码占用监视器刷新率的精确倍数,以避免多缓冲延迟。

仅仅呼叫pSwapChain->present(1,0)是不够的。这样可以防止在全屏模式下撕裂,但它不会等待vblank发生。当前调用是异步的,如果还有剩余的帧缓冲区,它会立即返回。因此,如果您的渲染代码非常快地生成一个新帧(例如10ms来渲染所有内容),并且用户已将驱动程序的“最大预渲染帧数”设置为4,那么您将在用户看到之前渲染四帧。这意味着鼠标操作和屏幕响应之间有4 * 16.7 = 67ms的延迟,这是不可接受的。请注意,驱动程序的设置获胜 - 即使您的应用程序要求pOutput->setMaximumFrameLatency(1),您也可以获得4帧。因此,无论驱动程序设置如何,保证无鼠标延迟的唯一方法是让渲染循环自动等到下一个垂直刷新间隔,这样你就不会使用那些额外的frameBuffers。

IDXGIOutput::WaitForVBlank()旨在用于此目的。但它不起作用!当我打电话给以下

<render something in ~10ms>
pSwapChain->present(1,0);
pOutput->waitForVBlank();

并且我测量waitForVBlank()调用返回所花费的时间,我看到它大致在6ms到22ms之间交替。

怎么会发生这种情况? waitForVBlank()怎么可能需要超过16.7毫秒才能完成?在DX9中,我们使用getRasterState()解决了这个问题,以实现我们自己的,更精确的waitForVBlank版本。但是这个电话在DX11中被弃用了。

还有其他方法可以保证我的画面与显示器的刷新率完全一致吗?是否有其他方法可以监视当前扫描线,就像过去使用getRasterState一样?

答案 1 :(得分:2)

  

我之前尝试使用OpenGL,使用WGL_EXT_swap_control扩展,通过绘制然后调用

SwapBuffers(g_hDC);
glFinish();

glFinish()或glFlush是多余的。 SwapBuffers意味着glFinish。

可能是,在您的图形驱动程序设置中设置“强制V-Blank / V-Sync关闭”?

答案 2 :(得分:1)

我们目前使用DX9,并希望切换到DX11。我们目前使用GetRasterState()手动同步到屏幕。这在DX11中消失了,但我发现制作DirectDraw7设备似乎并没有破坏DX11。因此,只需将其添加到您的代码中,您就可以获得扫描线位置。

IDirectDraw7* ddraw = nullptr;
DirectDrawCreateEx( NULL, reinterpret_cast<LPVOID*>(&ddraw), IID_IDirectDraw7, NULL );
DWORD scanline = -1;
ddraw->GetScanLine( &scanline );

答案 3 :(得分:1)

在Windows 8.1和Windows 10上,您可以使用DXGI 1.3 DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT。见MSDN。这里的示例适用于Windows 8商店应用程序,但它也应该适用于Win32类Windows交换链。

您可能会发现此video也很有用。

答案 4 :(得分:0)

创建Direct3D设备时,请将PresentationInterval结构的D3DPRESENT_PARAMETERS参数设置为D3DPRESENT_INTERVAL_DEFAULT

答案 5 :(得分:-1)

如果你在kernel-mode或ring-0中运行,你可以尝试从VGA input register(03bah,03dah)读取第3位。这些信息已经相当陈旧,但虽然hinted here该位可能已经改变了位置,或者可能在Windows 2000及更高版本的版本中被淘汰,但实际上我对此表示怀疑。第二个链接有一些非常古老的源代码,它们试图为旧的Windows版本公开vblank信号。它不再运行,但理论上用最新的Windows SDK重建它应该解决这个问题。

困难的部分是构建和注册设备驱动程序,该驱动程序可靠地公开此信息,然后从您的应用程序中获取它。