Direct3D 11缺少GetRasterStatus函数,我该如何检测垂直消隐期?

16

我正在更新一个应用程序,其中屏幕上刺激呈现时间的测量需要最高的准确性。它目前使用DirectDraw编写,但DirectDraw已经被淘汰了很久,我们需要更新我们的图形库。

我们测量呈现时间的方式利用检测垂直空白期的结束。具体来说,我需要知道在主表面(或呈现在交换链中)翻转的任何内容实际上被屏幕绘制时,以最大可能的精度。检测扫描线可以增加该测量的确定性,但我只能使用在Flip或Present调用后立即检测到垂直空白期结束的方法。

Direct3D 9有IDirect3DDevice9 :: GetRasterStatus方法 ,返回包括InVBlank布尔值(描述设备是否处于垂直空白状态)和当前扫描线的D3DRASTER_STATUS结构。DirectDraw也有类似的功能(IDirectDraw :: GetVerticalBlankStatus,还可以使用IDirectDraw :: GetScanLine,在垂直空白期间返回DDERR_VERTICALBLANKINPROGRESS来检测VB)。

但是我没有找到Direct3D11中任何类似的功能。有人知道这个功能是否在Direct3D9和Direct3D11之间移动或删除了吗?如果是后者,为什么?


我不了解DirectX,但是你不能像在OpenGL中那样刷新管道吗?这样可以确保所有对DX11的调用都被执行了吧?而且Present()函数不是只有在缓冲区被复制/翻转后才返回吗? - Pillum
2
您可以在D3D 10和11上使用[IDXGIOutput :: WaitForVBlank](http://msdn.microsoft.com/en-us/library/bb174559%28v=vs.85%29)来等待垂直同步。也许这可以帮助您。 - pearcoding
4个回答

10
抱歉回复晚了,但我注意到还没有被接受的答案,所以也许你从未找到有效的解决方案。现在在Windows上,DesktopWindowManager服务(dwm.exe)协调一切,无法真正绕过。自从Windows 8以来,该服务就无法禁用。
因此,DWM始终会控制所有各种IDXGISurface(n)对象和IDXGIOutput(n)监视器的帧速率、渲染队列管理和最终合成,并且在追踪离屏渲染目标的VSync方面没有太大用处,除非我漏了什么(无意挖苦)。至于您的问题,我不确定您的目标是:
  1. 获取极其精确的时间信息,但仅用于诊断、分析或信息用途,或者
  2. 应用程序是否会(尝试)使用这些结果来(尝试)安排自己的当前周期。

如果是后者,我认为您只能在D3D应用程序运行在全屏独占模式下有效地执行此操作。这是唯一一种情况,DWM - 以DXGI的形式 - 将真正信任客户端处理自己的Present计时的情况。

这里的好消息(勉强算是)是,如果你对于VSync只是纯粹的信息需求——也就是说你属于上面所提到的第一类——那么你确实可以获得所有你想要的时间数据,并且在QueryPerformanceFrequency分辨率下,通常约为320 ns.¹

以下是如何获取高分辨率视频定时信息。但需要再次明确的是,尽管下面的结果显示已经成功获取了信息,但任何试图使用这些有趣的结果的尝试,例如将某些确定性的——因此可能有用的——结果条件化为你所获得的读数,都将注定失败,即完全被DWM中介所挫败:

DWM_TIMING_INFO

指定桌面窗口管理器(DWM)合成时序信息。由DwmGetCompositionTimingInfo函数使用。

typedef struct _DWM_TIMING_INFO
{
    UINT32    cbSize;                 // size of this DWM_TIMING_INFO structure
    URATIO    rateRefresh;            // monitor refresh rate
    QPC_TIME  qpcRefreshPeriod;       // monitor refresh period
    URATIO    rateCompose;            // composition rate
    QPC_TIME  qpcVBlank;              // query performance counter value before the vertical blank
    CFRAMES   cRefresh;               // DWM refresh counter
    UINT      cDXRefresh;             // DirectX refresh counter
    QPC_TIME  qpcCompose;             // query performance counter value for a frame composition
    CFRAMES   cFrame;                 // frame number that was composed at qpcCompose
    UINT      cDXPresent;             // DirectX present number used to identify rendering frames
    CFRAMES   cRefreshFrame;          // refresh count of the frame that was composed at qpcCompose
    CFRAMES   cFrameSubmitted;        // DWM frame number that was last submitted
    UINT      cDXPresentSubmitted;    // DirectX present number that was last submitted
    CFRAMES   cFrameConfirmed;        // DWM frame number that was last confirmed as presented
    UINT      cDXPresentConfirmed;    // DirectX present number that was last confirmed as presented
    CFRAMES   cRefreshConfirmed;      // target refresh count of the last frame confirmed as completed by the GPU
    UINT      cDXRefreshConfirmed;    // DirectX refresh count when the frame was confirmed as presented
    CFRAMES   cFramesLate;            // number of frames the DWM presented late
    UINT      cFramesOutstanding;     // number of composition frames that have been issued but have not been confirmed as completed
    CFRAMES   cFrameDisplayed;        // last frame displayed
    QPC_TIME  qpcFrameDisplayed;      // QPC time of the composition pass when the frame was displayed
    CFRAMES   cRefreshFrameDisplayed; // vertical refresh count when the frame should have become visible
    CFRAMES   cFrameComplete;         // ID of the last frame marked as completed
    QPC_TIME  qpcFrameComplete;       // QPC time when the last frame was marked as completed
    CFRAMES   cFramePending;          // ID of the last frame marked as pending
    QPC_TIME  qpcFramePending;        // QPC time when the last frame was marked as pending
    CFRAMES   cFramesDisplayed;       // number of unique frames displayed
    CFRAMES   cFramesComplete;        // number of new completed frames that have been received
    CFRAMES   cFramesPending;         // number of new frames submitted to DirectX but not yet completed
    CFRAMES   cFramesAvailable;       // number of frames available but not displayed, used, or dropped
    CFRAMES   cFramesDropped;         // number of rendered frames that were never displayed because composition occurred too late
    CFRAMES   cFramesMissed;          // number of times an old frame was composed when a new frame should have been used but was not available
    CFRAMES   cRefreshNextDisplayed;  // frame count at which the next frame is scheduled to be displayed
    CFRAMES   cRefreshNextPresented;  // frame count at which the next DirectX present is scheduled to be displayed
    CFRAMES   cRefreshesDisplayed;    // total number of refreshes that have been displayed for the application since the DwmSetPresentParameters function was last called
    CFRAMES   cRefreshesPresented;    // total number of refreshes that have been presented by the application since DwmSetPresentParameters was last called
    CFRAMES   cRefreshStarted;        // refresh number when content for this window started to be displayed
    ULONGLONG cPixelsReceived;        // total number of pixels DirectX redirected to the DWM
    ULONGLONG cPixelsDrawn;           // number of pixels drawn
    CFRAMES   cBuffersEmpty;          // number of empty buffers in the flip chain
}
DWM_TIMING_INFO;

(注:为了在此网站上水平压缩上面的源代码,请假设以下缩写已添加到前面。)
typedef UNSIGNED_RATIO URATIO;
typedef DWM_FRAME_COUNT CFRAMES;

现在对于以窗口模式运行的应用程序,您可以随时获取此详细信息。 如果您只需要它进行被动分析,则从DwmGetCompositionTimingInfo获取数据是现代化的方法。
而且,说到现代化,既然问题暗示了现代化,您会想要考虑使用从IDXGIFactory2::CreateSwapChainForComposition获得的IDXGISwapChain1来启用新的DirectComposition组件的使用。

DirectComposition 通过使用图形硬件以及独立于 UI 线程的方式实现高帧率,从而实现丰富流畅的转换。DirectComposition 可以接受由不同渲染库绘制的位图内容,包括 Microsoft DirectX 位图和绘制到窗口 (HWND 位图) 的位图。此外,DirectComposition 支持各种变换,例如二维仿射变换和三维透视变换,以及基本效果,如裁剪和不透明度。

无论如何,详细的时间信息似乎很少对应用程序的运行时行为有用;可能它会帮助您预测下一个 VSync,但是一个人确实会想知道 "对消隐期的敏锐感知" 对某个特定的 DWM-受制于屏幕外交换链有什么意义。

因为您的应用程序表面只是 DWM 正在处理的许多表面之一,DWM将会进行各种动态自适应,假定每个客户端都具有一致的行为。在这样的统治下,不可预测的自适应会不合作,并且很可能会使双方都困惑。




注意:
1. 尽管DateTime使用100纳秒单位,但QPC的分辨率比它高几个数量级。将DateTime.Now.Ticks视为重新打包(以毫秒表示)的Environment.TickCount,但转换为100纳秒单位。为获得最高分辨率,请使用静态方法Stopwatch.GetTimestamp()而不是DateTime.Now.Ticks


2
我发现这是非常有用的信息。OP的语言表明了一个应用程序,就像我的应用程序一样,它呈现一个刺激并测量与刺激时间锁定的生理反应。例如,解码脑干视觉诱发电位取决于微秒精度知道光子何时击中视网膜。在这种情况下,预测垂直同步信号并不重要,因此这个期待已久的答案是完全正确的! - Craig.Feied
你没有提到你的应用程序是否在全屏独占模式下运行。如前所述,这是实现您目标的唯一正确方式。如果在DWM中窗口化,您可能可以通过设置Windows显示设置的高刷新率,关闭图形卡中的各种干扰“功能”,然后让您的程序尝试根据DWM向您报告的内容进行被动预测来解决问题。显然,这种方法将非常hacky。 - Glenn Slayden
Glenn,在全屏独占模式下运行时,你知道在运行Unity应用程序时是否有一种方法可以获取DirectX垂直消隐信息吗? - tofutim
@tofutim 如果你在询问如何绕过Unity的某些限制,我对此一无所知,从未研究过Unity。作为一个DWM绑定用户,我也不太了解全屏独占模式,但我会假设在这种情况下完整的硬件访问和/或DirectX功能是解锁并可用的。 - Glenn Slayden

4

另外一个选择:

有一个D3DKMTGetScanLine()函数,它适用于D3D9,D3D10,D3D11,D3D12,甚至是OpenGL。实际上,它是一个GDI32函数,所以您可以利用Windows现有的图形hAdaptor来轮询VBlank / Scanline - 无需创建Direct3D帧缓冲区。这就是为什么这个API调用的D3D前缀,尽管它也可以与OpenGL、Mantle和非Direct3D渲染器一起使用。

它还告诉您VBlank状态和光栅扫描线。

在极为“延迟至关重要”的应用程序中,这对于光束赛车应用程序非常有用。一些虚拟现实渲染器使用光束赛车技术,即使只有20毫秒的延迟,也可能导致愉悦的虚拟现实变为晕眩或令人作呕。

光束赛车是在显示器扫描完毕后实时渲染。在专业的延迟至关重要的应用程序中,您可以将Direct3D Present()到像素命中您的眼睛的延迟降至绝对最小值(仅需3毫秒)。

要了解光束赛车是什么,请参见https://www.wired.com/2009/03/racing-the-beam/ - 在图形芯片没有帧缓冲区的日子里,它曾经很常见,使得改善Atari 2600、Nintendo、Commodore 64等设备的图形成为必要。

有关更现代的光束赛车实现,请参见模拟器开发者的Lagless VSYNC ON算法


0
具体来说,我需要尽可能准确地知道,当任何东西被翻转到主表面(或在交换链中呈现)时,屏幕实际上正在绘制它。
祝你好运。
实际上,并没有保证您放入呈现队列的任何内容都会显示在屏幕上(!!);您可以使用缓冲区序列化呈现标志手动丢帧,或者NVIDIA可以为您完成(...感谢?)
DXGI交换链的翻转队列通常是FIFO,但流行的新驱动程序覆盖(例如FastSync)会优先考虑CPU端吞吐量,而不是显示您绘制的任何帧 :)
通常情况下,当交换链中充满未显示的图像且驱动程序在GPU之前进行n帧暂存命令时,您可以指望IDXGISwapChain::Present(...)开始阻塞。但是,如果强制启用FastSync,则Present永远不会阻塞,并且渲染前队列通过覆盖等待VBLANK的Swapchain中已完成的帧来刷新其工作。

连续快速完成的后续呈现不需要(也不会)扫描输出,因此它们与VBLANK的状态无关。

除非您自己实现速率限制以防止CPU在任何调用Present后立即暂存下一帧,否则您需要完全不同的范例来衡量帧状态。

D3D9Ex / DXGI支持Flip /全屏独占模式下的演示统计信息:

除非以下API表明它们已经这样做,否则帧实际上不会呈现给用户:

IDXGISwapChain::GetFrameStatistics(...)IDXGISwapChain::GetLastPresentCount(...)

您可以使用帧统计数据实时计算渲染队列/呈现延迟的长度,跟踪成功同步帧的帐户信息中的显示次数可能会满足您的时间目标。


-2
问题在于为什么?看起来你想解决你的问题的症状;也许这会分散你对真正问题的注意力。等待垂直同步在Amiga或DOS上是一种有用的技术。但在任何合成或多线程操作系统上都是完全错误的。
首先,你想要实现什么?无撕裂渲染可以通过在D3D或OpenGL上设置交换间隔来完成。试图比操作系统做得更好是有害的。只需考虑多个监视器的情况或如果超过一个应用程序尝试同步会发生什么。
如果你是其他进程的客户端,并且想在VSync上运行你的定时,很遗憾,据我所知,Windows没有提供任何可等待的对象。你最好还是依赖Present调用并估计正在发生的事情。
有两种情况:你要么比垂直同步快(呈现),要么比垂直同步慢。如果你更快,Present应该已经为你阻塞。如果Present从不等待,而你之间的时间大于1/60秒,则可能希望减少渲染频率。
人们关心VSync的最常见情况是视频。您可以比vsync更快地渲染,但想要等待恰当的时间呈现。唯一要做的就是尽可能快地运行几帧,并从中估计您的帧时序。使用一些抖动和反馈...或者使用内置硬件视频,它足够愉快,可以与视频驱动程序成为内核朋友。

5
这个问题非常合理,回答“你为什么问这个问题?”会让人感到不安。 - Lorenzo Pistone
@LorenzoPistone:也许你读了比前几句更多的内容?尽管开头有些困难,而且缺乏段落分割(我已经修复了后者),但这个答案是一颗宝石,提供了大量关于一个棘手和晦涩的D3D问题的信息,这个问题确实没有好的解决方案。 - Glenn Slayden
像Glen的答案没有考虑到光束竞赛渲染机制的可能性——在超低延迟关键应用程序中使用以减少输入延迟,在这些应用程序中,+20ms可能意味着天堂和呕吐/恶心之间的差异。有一些需要知道光栅位置的关键用例。此外,一些模拟器现在使用光束竞赛来减少输入延迟(请搜索“无延迟VSYNC”了解更多信息)。 - Mark Rejhon
1
@MarkRejhon,您是指这个答案吗?还是您在错误的地方发表了评论?这个答案是由“starmole”提供的,我只是进行了编辑。 - Glenn Slayden
@MarkRejhon 但是我想说的是,这不是我的答案,所以我不是那个问“问题是为什么?”的人。 - Glenn Slayden
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接