3

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

我将首先说我的视频例程基于 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 毫秒内绘制的,接下来是我的白色方块,但下一次刷新的确切时间是未知的。

从我所做的所有阅读中,我倾向于选项 3,但我找不到任何针对 4 的保证。在我的配置中,只有在第二次调用 Present 函数时才应该阻塞在两个刷新周期之间暂停。由于目标是交换前缓冲区和后缓冲区,因此第二次调用可以执行此操作的最早时间点是在刷新监视器之后(刚刚绘制了前一个前缓冲区)。到那时,包含我的白色方块的后缓冲区可以移动到前面,但它必须等待(最多)16.67 毫秒,然后监视器才能真正读取并显示缓冲区内容。理想情况下,我希望听到该函数应始终在上一个刷新周期完成后立即返回。

任何对 DirectX 更有经验的人可以提供有关此主题的任何见解吗?我的假设是正确的还是我遗漏了什么?对于任何支持 DirectX 的系统,这些假设是否总是正确的,或者逻辑是否会根据视频卡、显示器或其他一些东西而改变?

作为最后一个小问题,回到我一遍又一遍地调用 SDL_RenderPresent 的循环,我注意到前 3 或 4 个调用立即返回,而所有后续调用都在等待刷新周期。我是否正确假设 D3DPRESENT_INTERVAL_ONE 限制在第一次刷新之前被简单地忽略(与我期望的超过 2 个缓冲区发生的某种排队相反)?

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

[编辑]

根据下面的回复,很明显我使用 vsync 和 Present 的方法不起作用。我想我找到了另一种方法来达到预期的结果,所以我把它贴在这里,以防有人发现我的想法有错误,或者只是为了给其他从事类似问题工作的人提供信息。

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

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

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

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

我应该补充一点,我今天在工作中的几台计算机上测试了这个理论。大多数确实显示了图案,但至少有一个将整个屏幕涂成红色。少数会出现色带上下跳跃,表明在交换缓冲区时存在一些不一致。这通常发生在较旧的机器上。我认为这是一个很好的校准测试,可以确定硬件是否适合我们的测试目的。

4

1 回答 1

3

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

  • D3D 通常会缓冲超过一帧的渲染命令(包括呈现)。例如,请参见幻灯片 25,我们可以看到在 BumpEarth 设备队列中缓冲了大约 3 帧。这解释了 3-4 个调用立即返回(当前数据包是交叉的数据包)。他们只是排队。
  • 除非您正在进行全屏渲染,否则操作系统需要进行一些合成(同一张幻灯片显示了在 vsync 上发生的合成 - 蓝色垂直线)

一些后果:

  • 当前返回根本无法保证您刚刚发送的渲染命令何时会在屏幕上更新。
  • 您的命令渲染帧所需的持续时间并不容易弄清楚。我已经看到应用程序依赖于先前渲染的时序,经过平滑处理(以防止乒乓渲染更改)。

作为附加评论:

  • 我在现实生活中的工作负载中目睹了大约 1.5 帧的命令缓冲。
  • 即使发生 vsync 并且视频卡更新了前端缓冲区,显示器仍然可以在内部进行一些缓冲(因为我们将 CRT 抛在后面,所以更是如此)。

我不得不问,为什么你需要精确控制帧在屏幕上显示的时间?

于 2010-09-23T19:11:15.673 回答