Direct3D 11缺少GetRasterStatus,我如何检测垂直空白期



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

我们测量呈现时间的方法是检测垂直空白期的结束。我特别需要尽可能准确地知道,什么时候被翻转到主表面(或在交换链中呈现)实际上是由屏幕绘制的。检测扫描线可以增加测量的确定性,但我可以只检测在调用Flip或Present后垂直空白期立即结束的时间。

Direct 3D 9具有IDirect3DDevice9::GetRasterStatus方法,该方法返回一个D3DRASTER_STATUS结构体,其中包括一个InVBlank布尔值,该布尔值描述设备是否处于垂直空白,以及当前扫描线。DirectDraw有类似的功能(IDirectDraw::GetVerticalBlankStatus,还有IDirectDraw::GetScanLine,它在垂直空白期间返回DDERR_VERTICALBLANKINPROGRESS,可以用来检测VB)。

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

很抱歉回复晚了,但我注意到仍然没有接受的答案,所以也许你从来没有找到一个工作。现在在Windows上,DesktopWindowManager服务(dwm.exe)协调一切,不能真正绕过。从Windows 8开始,这个服务就不能被关闭了。

所以DWM总是要控制帧率,渲染队列管理,以及所有各种idxgisface (n)对象和IDXGIOutput(n)监视器的最终组成,并且在跟踪VSync用于屏幕外渲染目标方面没有太多用处,除非我错过了一些东西(没有讽刺的意图)。至于你的问题,我不确定你的目的是不是:

  1. 获得非常精确的定时信息,但仅用于诊断,分析或信息使用,或
  2. 应用程序是否打算(尝试)使用这些结果来(尝试)调度它自己当前的周期。

如果是后者,我相信只有在D3D应用程序以全屏独占模式运行时才能有效地执行此操作。这是dwm在DXGI的伪装下将真正信任客户端处理其自己的Present定时的唯一情况。

这里的(几乎)好消息是,如果您对VSync的兴趣仅是信息性的-也就是说,您属于上面的项目符类别(1.)-那么您确实可以获得您想要的所有定时数据,并且在queryperformancfrequency分辨率下,通常在320 ns左右。¹

这里是如何获得高分辨率视频时序信息。但是,再次说明一下,尽管在获得如下所示的信息方面取得了明显的成功,但任何使用这些有趣的结果(例如,对您获得的读数的某些确定性(因此可能有用)结果进行条件限制)的尝试都注定要失败,也就是说,完全被DWM中介所挫败:

DWM_TIMING_INFO

指定DWM (Desktop Window Manager)组合计时信息。由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支持各种变换,如2D仿射变换和3D透视变换,以及基本效果,如剪切和不透明度。

无论如何,详细的时间信息似乎不太可能有效地告知应用程序的运行时行为;也许它将帮助你预测你的下一个垂直同步,但是一个确实想知道"对空白期的敏锐意识"对于某些特定的dwm从属的屏幕外交换链可能有什么意义。

因为你的应用程序的表面只是DWM正在处理的众多问题之一,DWM将在每个客户端行为一致的假设下,自己进行各种动态适应。在这样的制度下,不可预测的适应是不合作的,很可能最终会让双方都感到困惑。




指出:
1。QPC的分辨率比DateTime的高许多个数量级,尽管后者暗示使用了100 ns。单位面值。可以把DateTime.Now.Ticks看作是(以毫秒表示的)Environment.TickCount的重新包装,但转换为100纳秒单位。为了获得尽可能高的分辨率,使用静态方法Stopwatch.GetTimestamp()而不是DateTime.Now.Ticks

另一种选择:

有D3DKMTGetScanLine()与D3D9, D3D10, D3D11, D3D12,甚至OpenGL一起工作。

它实际上是一个GDI32函数,所以你可以利用窗口现有的图形haaptor来轮询VBlank/Scanline——不需要创建Direct3D帧缓冲区。这就是为什么这个API可以很好地与OpenGL, Mantle和非direct3d渲染器一起工作,尽管这个API调用的前缀是D3D。

它还告诉你VBlank状态&光栅扫描线

对于在"延迟至关重要"的应用程序中进行波束竞速应用程序非常有用。一些虚拟现实渲染使用波束竞速,即使仅仅20毫秒的延迟也可能意味着令人愉快的VR和令人眼花缭乱/恶心的VR之间的差异。

波束竞速是在扫描显示后动态渲染的。在专门的延迟关键型应用程序中,您可以将Direct3D Present()到像素击中您的眼球的延迟减少到绝对最小(低至3ms)。

要理解什么是波束竞速,请访问https://www.wired.com/2009/03/racing-the-beam/——这在图像芯片没有帧缓冲的时候很常见——这使得波束竞速对于雅达利2600、任天堂、Commodore 64等游戏机的图像改进是必要的……

有关更现代的波束竞速实现,请参见模拟器的无Lagless VSYNC ON算法。

"我特别需要知道,尽可能准确,什么时候被翻转到主表面(或在交换链中呈现)实际上是由屏幕绘制的。"

<标题>好运。

实际上不能保证您放入当前队列的任何内容将在屏幕上显示(!!);您可以手动删除帧w/缓冲序列当前标志,或NVIDIA可以为您做(…谢谢 ?)

DXGI缓冲序列

DXGI交换链的翻转队列通常是FIFO,但流行的新驱动覆盖(即FastSync),用户关心延迟将最肯定地启用,支持cpu端吞吐量,而不是像显示任何你绘制的帧这样的微不足道的事情:)

通常情况下,你可以指望IDXGISwapChain::Present(…)开始阻塞时,交换链充满了未显示的图像,驱动程序是在GPU前面n-许多帧的阶段命令,但与FastSync强制,Present永远不会阻塞和渲染前队列刷新它的工作覆盖任何完成的帧在交换链等待VBLANK。

背对背表示完全比屏幕刷新快,没有义务(也不会)扫描,因此它们的状态相对于VBLANK是没有意义的。

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

D3D9Ex/DXGI支持翻转/全屏显示统计

帧并不实际呈现给用户,除非下面的api这样说:

IDXGISwapChain::GetFrameStatistics(…)和IDXGISwapChain::GetLastPresentCount(…)

您可以使用帧统计来实时计算呈现队列的长度/呈现延迟,并且您的时间目标可能可以通过跟踪当前#来满足成功同步帧的会计信息。

问题是为什么?看起来你想解决问题的一个症状;也许这能分散你对真正问题的注意力。等待垂直同步在Amiga或DOS上是一项有用的技术。这在任何合成或多线程操作系统上都是完全错误的。

首先,你想达到什么目标?无撕裂渲染是通过在D3D或OpenGL上设置交换间隔来完成的。试图做得比操作系统更好是有害的。想想多个显示器的情况,或者如果多个应用程序试图同步会发生什么。

如果你是其他进程的客户端,想要在VSync上运行定时,不幸的是,据我所知,Windows没有提供对象来等待。最好的办法是仍然依赖于Present调用并估计正在发生的事情。

有两种情况:您的渲染(呈现)速度比vsync快或慢。如果你比较快,Present应该已经为你屏蔽了。如果present从不等待,并且你的呼叫间隔时间超过1/60秒,你可能想要更少地渲染。

为什么人们关心垂直同步最常见的情况是视频。你可以渲染得比垂直同步快得多,但要等待合适的时间来呈现。唯一要做的就是尽可能快地运行几帧,然后根据这个估计来计算帧计时。使用一些抖动和反馈……或者使用内置的硬件视频,它很乐意成为视频驱动程序的内核朋友。

最新更新