2010-09-23 57 views
2

我正在开发一个科学应用程序,它必须估计(尽可能最佳)在视频缓冲区中绘制的对象与该对象实际上在屏幕上变得可见。换句话说,Windows XP +上的DirectX如何处理显示器的垂直刷新周期。了解IDirect3DDevice9 ::当它阻止vsync时的行为

我首先说我的视频例程基于SDL 1.3库。因此,我无法立即访问DirectX API,但如有必要,可以对其进行更改。在全屏模式下,DirectX正在使用D3DSWAPEFFECT_DISCARD,D3DPRESENT_INTERVAL_ONE和BackBufferCount = 1进行初始化。这些似乎是最关键的参数,但如果需要更多信息,我很乐意深入其他SDL代码。

D3DPRESENT_INTERVAL_ONE标志确保后端和前端缓冲区在每个刷新周期内交换不超过一次,并且从不在刷新过程中(基本上启用vsync)。事实上,如果我有一个简单的循环,只是不断调用IDirect3DDevice9 :: Present(在我的情况下为SDL_RenderPresent),该函数将阻塞两个刷新周期之间的毫秒数(60Hz为16.67ms,100Hz为10ms等) 。

这是我的问题......假设我在后台缓冲区中绘制一个白色正方形,并调用SDL_RenderPresent,它将阻塞16.67毫秒(假设刷新60Hz)。当对SDL_RenderPresent的调用返回时,我可以得出有关监视器上可见图像状态的结论吗?以下是可能的,因为我看到它:

  1. 白色方块是只是绘制的监视器上。
  2. 白色正方形是要绘制(小于1毫秒)。
  3. 上一个前台缓冲区刚绘制完毕;它会在我的白色方块出现之前花费另一个刷新周期(16.67 ms)(再次调用SDL_RenderPresent会使我处于情况1)。
  4. 上一个前缓冲区是在最后的16.67 ms中绘制的,我的白色方块是下一个,但直到下一次刷新的确切时间未知。

从我所做的所有阅读中,我倾向于选项3,但我无法找到针对4的任何保证。在我的配置中,Present函数应该仅在被调用在两个刷新周期之间的暂停期间第二次。由于目标是交换前后缓冲区,所以第二次调用可以完成的最早点就是刷新监视器之后(之前的前端缓冲区刚刚绘制完毕)。正是在这一点上,包含我的白方块的后台缓冲区可以移动到前端,但它必须等待(最多)16.67毫秒,然后显示器才会真正读取并显示缓冲区内容。理想情况下,我希望听到该函数应该在前一个刷新周期结束后立即返回。

任何对DirectX更有经验的人都可以提供关于此主题的任何见解吗?我的假设是正确的还是我错过了什么?对于任何具有DirectX支持的系统,这些假设是否总是正确的?或者逻辑是否会根据视频卡,显示器或其他因素而改变?作为最后一个小问题,回到刚刚调用SDL_RenderPresent的循环中,我注意到前3或4个调用会立即返回,而所有后续的调用将等待刷新周期。我是否正确地认为D3DPRESENT_INTERVAL_ONE限制在第一次刷新之前简单地被忽略(而不是某种排队发生在我期望拥有的超过2个缓冲区的地方)?

换句话说,假设循环进入〜8ms直到下一个刷新周期。在此期间,它可能会交换正面和背面缓冲区4次。在第一次刷新发生之前,SDL_RenderPresent将立即返回(因为我们在技术上目前没有任何前端缓冲区,只有2个后端缓冲区),但只要其中一个缓冲区显示在屏幕上,阻塞就会开始发生。这是否是一个有效的解释?

基于下面的回复[编辑]

,它在使用VSYNC和现在不工作,我的办法很清楚。我想我找到了另一种达到预期结果的方式,所以我在这里发布它,以防有人发现我的想法中的错误,或者仅仅是为了解决类似问题的其他人的信息。

第一步是摆脱D3DPRESENT_INTERVAL_ONE。这将禁用vsync并确保任何对SDL_RenderPresent的调用将立即返回。接下来,您可以使用IDirect3DDevice9 :: GetRasterStatus获取有关当前监视器状态的信息。它提供了一个布尔字段,在两个刷新周期之间的暂停期间设置为true,另一个字段在活动刷新期间告诉您当前扫描行。使用这两个信息可以实现自己的垂直同步例程,尽管通过运行一个不断轮询监视器状态并因此消耗100%CPU的循环。这对我的需求是可以接受的。

还有缓冲的问题 - 我怎么知道当我调用SDL_RenderPresent时在屏幕上绘制哪个帧?我想我找到了一种方法来确定这一点,这依赖于我能够知道显示器上当前正在绘制哪条线。基本逻辑如下:

  1. 等待新的刷新周期开始(pause = false,scanline = 0)。
  2. 用红色填充下一个后台缓冲区并调用Present。
  3. 等待扫描线达到32.
  4. 用绿色填充下一个后台缓冲区并调用Present。

等等......在我的演示实现中,我使用了红色,绿色,蓝色,最后是黑色。这个想法是,如果GetRasterStatus提供有关刷新状态的准确信息,并且在调用SDL_RenderPresent时立即翻转前后缓冲区,则只会看到RGB颜色模式。如果这些条件中的任何一个都不符合,您可能看不到任何东西,颜色可能会交换或重叠,等等。另一方面,如果您在每个画面的顶部看到一个固定的RGB图案,那么这个证明您可以直接控制绘制的图像。

我应该补充一点,我在今天工作的几台电脑上测试了这个理论。大多数人都展示了这种模式,但至少有一个人把整个屏幕涂成了红色。少数人会有颜色带上下跳动,表明交换缓冲区时有些不一致。这通常发生在较旧的机器上。我认为这是一个很好的校准测试,以确定硬件是否适合我们的测试目的。

回答

3

我强烈建议你看看微软的GPUView。这里是介绍该工具的one of the authors webpage

  • D3D通常会缓冲一帧以上的渲染命令(包括礼物)。例如,请参见幻灯片25,我们可以在BumpEarth设备队列中看到〜3帧被缓冲。这解释了3-4个呼叫立即返回(当前包是交叉的)。他们刚刚排队。
  • 除非你做全屏渲染,操作系统需要做一些合成(同一幻灯片显示了合成发生在垂直同步 - 蓝色垂直线)

一些后果:

  • 目前的回归并不能保证你的刚刚发送的渲染命令在屏幕上更新。
  • 您的命令渲染帧的持续时间并不容易理解。我已经看到应用程序依赖于以前渲染的时序,进行了平滑处理(以防止乒乓渲染更改)。

作为补充意见:在现实生活中的工作负载指令缓冲

  • 我目睹〜1.5框架的价值。
  • 即使发生vsync并且视频卡更新前端缓冲区,监视器仍然可以在内部做一些缓冲(更重要的是,因为我们将CRT放在后面)。

我必须问,为什么你需要准确地控制帧显示在屏幕上?

+0

感谢您的解释(尽管这是一个坏消息)。我正在编写测量人类对科学研究反应时间的软件。理想情况下,我希望误差幅度降至1毫秒,但我怀疑如果没有一些专门的硬件和实时操作系统,这将是可能的。它最终可能会达成。 如果无法控制DirectX如何缓冲帧,通过完全禁用缓冲区和vsync并使用IDirect3DDevice9 :: GetRasterStatus来确定何时可以安全地更新屏幕,是否可以获得更好的结果? – VokinLoksar 2010-09-23 19:31:54

+0

您可以使用查询来确保frmae已经被刷新到显卡......尽管如此,您仍然无法百分之百地确信...一些TFT屏幕也有有趣的延迟。您最好使用高速相机,然后拍摄屏幕和用户。然后对帧进行计数直到用户作出反应。一个300fps的相机会给你3毫秒的精度,所以如果你可以得到一个1000fps的相机,你会有你的〜1ms ...在不知道更多关于你的系统的代码的情况下做到这一点是不可能的,尽管... – Goz 2010-09-24 21:44:25

+0

链接已死。你能更新吗? – 2015-11-17 20:22:17

相关问题