了解IDirect3DDevice9 ::当它阻塞vsync时的行为

我正在开发一个科学应用程序,它必须(尽可能)估计在video后台缓冲区中绘制的对象与在屏幕上实际变为可见的点之间的时间差。 换句话说,Windows XP +上的DirectX如何处理显示器的垂直刷新周期。

我将首先说我的video例程基于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毫秒)将需要另一个刷新周期(再次调用SDL_RenderPresent会使我遇到情况1)。
  4. 之前的前台缓冲区是在最后的16.67 ms中绘制的,我的白色方块是下一个,但是直到下一次刷新的确切时间是未知的。

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

有没有人更有经验的DirectX提供任何见解这个话题? 我的假设是正确的还是我错过了什么? 对于任何具有DirectX支持的系统,这些假设是否总是正确的?或者逻辑是否会根据显卡,显示器或其他因素而改变?

作为最后一个小问题,回到刚刚调用SDL_RenderPresent的循环中,我注意到前3或4个调用立即返回,而后面的所有调用都等待刷新周期。 我是否正确地认为D3DPRESENT_INTERVAL_ONE限制在第一次刷新之前就被忽略了(而不是像我期待的那样有超过2个缓冲区发生某种排队)?

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

[编辑]

基于下面的回复,很明显,我的方法使用vsync和Present不起作用。 我想我find了另一种方法来达到预期的效果,所以我在这里发布,以防有​​人发现我的想法中的错误,或者只是为了解决类似问题的其他人的信息。

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

仍然存在缓冲的问题 – 当我调用SDL_RenderPresent时,如何知道在屏幕上绘制哪个帧? 我认为我find了一种方法来确定这一点,这依赖于我的能力,知道显示器上当前正在绘制哪一行。 这是基本的逻辑:

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

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

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

我强烈建议你看看微软的GPUView。 这是介绍该工具的作者网页之一。

  • D3D通常会缓存多于一帧的渲染命令(包括礼物)。 有关示例,请参见幻灯片25,我们可以在BumpEarth设备队列中看到〜3帧被缓冲。 这就解释了3-4个呼叫立即返回(现在的数据包是交叉的)。 他们只是排队。
  • 除非进行全屏渲染,否则操作系统需要进行一些合成(相同的幻灯片显示了vsync上的合成 – 垂直蓝线)

一些后果:

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

作为补充评论:

  • 我目睹了〜1.5帧的实际工作负载下的命令缓冲。
  • 即使发生vsync并且显示卡更新了前端缓冲区,显示器仍然可以在内部做一些缓冲(更多的是因为我们把CRT放在后面)。

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