12

我正在尝试进行垂直同步渲染,以便每次垂直同步完成一次渲染,而不会跳过或重复任何帧。我需要它在 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_pSwapChaina是IDXGISwapChain*与该 SwapChain 关联的。pOutputIDXGIOutput*

OpenGL 和 Direct3D 这两个版本的结果是一样的:第一个序列,比如 60 帧,并没有持续应有的时间(而不是在 60hz 下大约 1000 毫秒,而是持续大约 1030 或 1050 毫秒),以下的似乎工作正常(大约 1000.40 毫秒),但它似乎时不时地跳过一帧。我用 进行测量QueryPerformanceCounter

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

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

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

编辑:只有 60 倍 的测试循环WaitForVSync需要从 1000.30 毫秒到 1000.50 毫秒。Present(1,0)与before相同的循环WaitForVSync,没有其他内容,没有渲染,花费相同的时间,但有时它会失败并花费 1017 毫秒,就好像重复了一帧一样。没有渲染,所以这里有问题。

4

6 回答 6

3

我在 DX11 也有同样的问题。我想保证我的帧渲染代码采用显示器刷新率的精确倍数,以避免多缓冲延迟。

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

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

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

我测量了waitForVBlank()调用返回所需的时间,我看到它大约在 6 毫秒和 22 毫秒之间交替。

怎么会这样?怎么可能waitForVBlank()需要超过 16.7 毫秒才能完成?在 DX9 中,我们通过getRasterState()实现我们自己的更准确的 waitForVBlank 版本解决了这个问题。但是那个调用在 DX11 中被弃用了。

有没有其他方法可以保证我的帧与显示器的刷新率完全一致?是否有另一种方法可以像 getRasterState 那样监视当前的扫描线?

于 2014-04-18T21:13:13.177 回答
1

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

SwapBuffers(g_hDC);
glFinish();

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

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

于 2012-05-08T14:28:38.003 回答
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 );
于 2015-09-22T22:24:01.337 回答
1

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

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

于 2015-10-06T23:42:30.810 回答
0

创建 Direct3D 设备时,将结构的PresentationInterval参数设置为.D3DPRESENT_PARAMETERSD3DPRESENT_INTERVAL_DEFAULT

于 2012-05-08T14:24:59.500 回答
-1

如果您在内核模式或 ring-0 下运行,您可以尝试从VGA 输入寄存器(03bah,03dah) 读取位 3。该信息相当陈旧,但尽管此处暗示该位可能已更改位置或可能在更高版本的 Windows 2000 及更高版本中已过时,但我实际上对此表示怀疑。第二个链接有一些非常古老的源代码,它们试图公开旧 Windows 版本的 vblank 信号。它不再运行,但理论上用最新的 Windows SDK 重建它应该可以解决这个问题。

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

于 2015-09-02T10:49:25.143 回答