翻转模型,脏矩形,滚动区域

DXGI 1.2 支持新的翻转模型交换链、脏矩形和滚动区域。 我们介绍了使用新的翻转模型交换链以及通过指定脏矩形和滚动区域来优化呈现的好处。

DXGI 翻转模型演示

DXGI 1.2 添加了对 Direct3D 10 及更高版本 API 的翻转演示模型的支持。 在 Windows 7 中,Direct3D 9EX 首先采用了 翻转模型呈现 ,以避免不必要地复制交换链缓冲区。 通过使用翻转模型,后台缓冲区在运行时和桌面窗口管理器 (DWM) 之间翻转,因此 DWM 始终直接从后台缓冲区撰写,而不是复制后台缓冲区内容。

DXGI 1.2 API 包括经修订的 DXGI 交换链接口 IDXGISwapChain1。 可以使用多个 IDXGIFactory2 接口方法创建相应的 IDXGISwapChain1 对象,以便与 HWND 句柄、 CoreWindow 对象、 DirectCompositionWindows.UI.Xaml 框架一起使用。

通过指定 DXGI_SWAP_CHAIN_DESC1 结构的 SwapEffect 成员中的 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 枚举值,并将 DXGI_SWAP_CHAIN_DESC1BufferCount 成员设置为最小值 2,可以选择翻转表示模型。 有关如何使用 DXGI 翻转模型的详细信息,请参阅 DXGI 翻转模型。 由于翻转演示文稿模型的更流畅的演示和其他新功能,我们建议你将翻转演示文稿模型用于使用 Direct3D 10 及更高版本 API 编写的所有新应用。

在交换链演示文稿中使用脏矩形和滚动矩形

通过在交换链演示文稿中使用脏矩形和滚动矩形,可以节省内存带宽的使用情况和系统电源的相关使用,因为如果操作系统不需要绘制整个帧,则操作系统绘制下一帧所需的像素数据量会减少。 对于通常通过远程桌面连接和其他远程访问技术显示的应用,由于这些技术使用脏矩形和滚动元数据,因此在显示质量方面尤其明显。

只能对在翻转演示文稿模型中运行的 DXGI 交换链使用滚动。 可以将脏矩形与 DXGI 交换链一起使用,这些交换链在翻转模型和 bitblt 模型 (使用 DXGI_SWAP_EFFECT_SEQUENTIAL) 设置。

在此方案和插图中,我们演示了使用脏矩形和滚动的功能。 此处,可滚动应用包含文本和动画视频。 应用使用脏矩形来仅更新窗口的动画视频和新行,而不是更新整个窗口。 滚动矩形允许操作系统复制和转换新帧上以前呈现的内容,并仅呈现新框架上的新行。

应用通过调用 IDXGISwapChain1::P resent1 方法执行演示。 在此调用中,应用将指针传递给DXGI_PRESENT_PARAMETERS结构,该结构包括脏矩形和脏矩形的数目,或者滚动矩形和关联的滚动偏移量,或者同时脏矩形和滚动矩形。 我们的应用传递 2 个脏矩形和滚动矩形。 滚动矩形是操作系统在呈现当前帧之前需要复制到当前帧的上一帧的区域。 应用将动画视频和新线指定为脏矩形,操作系统在当前帧上呈现它们。

滚动和脏矩形重叠的插图

DirtyRectsCount = 2
pDirtyRects[ 0 ] = { 10, 30, 40, 50 } // Video
pDirtyRects[ 1 ] = { 0, 70, 50, 80 } // New line
*pScrollRect = { 0, 0, 50, 70 }
*pScrollOffset = { 0, -10 }

虚线矩形显示当前框架中的滚动矩形。 滚动矩形由 DXGI_PRESENT_PARAMETERSpScrollRect 成员指定。 箭头显示滚动偏移量。 滚动偏移量由 DXGI_PRESENT_PARAMETERSpScrollOffset 成员指定。 填充矩形显示应用使用新内容更新的脏矩形。 填充矩形由 DXGI_PRESENT_PARAMETERSDirtyRectsCountpDirtyRects 成员指定。

包含脏矩形和滚动矩形的示例 2 缓冲区翻转模型交换链

下一个插图和序列显示了使用脏矩形和滚动矩形的 DXGI 翻转模型演示操作的示例。 在此示例中,我们使用翻转模型演示的最小缓冲区数,即两个缓冲区计数,一个前缓冲区包含应用显示内容,一个后台缓冲区包含应用要呈现的当前帧。

  1. 如帧开头的前面缓冲区所示,可滚动应用最初显示包含一些文本和动画视频的框架。
  2. 为了呈现下一帧,应用会将脏矩形呈现到后台缓冲区,用于更新动画视频和窗口的新行。
  3. 当应用调用 IDXGISwapChain1::P resent1 时,它会指定脏矩形以及滚动矩形和偏移量。 接下来,运行时将上一帧中的滚动矩形减去更新脏矩形复制到当前后台缓冲区。
  4. 运行时最终交换前端和后端缓冲区。

具有滚动和脏矩形的翻转模型交换链示例

跨多个帧跟踪脏矩形和滚动矩形

在应用中使用脏矩形时,必须跟踪脏矩形以支持增量呈现。 当应用使用脏矩形调用 IDXGISwapChain1::P resent1 时,必须确保脏矩形中的每个像素都是最新的。 如果未完全重新呈现脏矩形的整个区域,或者无法知道某些被弄脏的区域,则必须在开始呈现之前,将一些数据从以前的完全一致的后台缓冲区复制到当前过时的后台缓冲区。

运行时仅将上一帧的更新区域与当前帧的更新区域之间的差异复制到当前后台缓冲区。 如果这些区域相交,运行时仅复制它们之间的差异。 如下图和序列所示,必须将第 1 帧脏矩形与第 2 帧脏矩形之间的交集复制到帧 2 的脏矩形中。

  1. 在框架 1 中显示脏矩形。
  2. 将框架 1 中的脏矩形与第 2 帧脏矩形之间的交集复制到帧 2 的脏矩形中。
  3. 在框架 2 中呈现脏矩形。

跨多个帧跟踪滚动和脏矩形

通用化,对于具有 N 个缓冲区的交换链,运行时从最后一帧复制到当前帧上当前帧的区域为:

用于计算运行时复制的区域的公式

其中,buffer 指示交换链中的缓冲区索引,从零处的当前缓冲区索引开始。

可以通过保留上一帧的脏矩形的副本或使用上一帧中的相应内容重新呈现新帧的脏矩形来跟踪上一帧与当前帧脏矩形之间的任何交集。

同样,在交换链具有 2 个以上的后台缓冲区的情况下,必须确保复制或重新呈现当前缓冲区的脏矩形与所有上一帧的脏矩形之间的重叠区域。

跟踪 2 个脏矩形之间的单个交集

在最简单的情况下,每帧更新单个脏矩形时,两个帧之间的脏矩形可能会相交。 若要确定上一帧的脏矩形与当前帧的脏矩形是否重叠,需要验证上一帧的脏矩形是否与当前帧的脏矩形相交。 可以调用 GDI IntersectRect 函数来确定表示两个脏矩形的两个 RECT 结构是否相交。

在此代码片段中,对 IntersectRect 的调用返回另一个名为 dirtyRectCopy 的另一个 RECT 中两个脏矩形的交集。 在代码片段确定两个脏矩形相交后,它将调用 ID3D11DeviceContext1::CopySubresourceRegion1 方法将交集区域复制到当前帧中。

RECT dirtyRectPrev, dirtyRectCurrent, dirtyRectCopy;
 
if (IntersectRect( &dirtyRectCopy, &dirtyRectPrev, &dirtyRectCurrent ))
{
       D3D11_BOX intersectBox;
       intersectBox.left    = dirtyRectCopy.left;
       intersectBox.top     = dirtyRectCopy.top;
       intersectBox.front   = 0;
       intersectBox.right   = dirtyRectCopy.right;
       intersectBox.bottom  = dirtyRectCopy.bottom;
       intersectBox.back    = 1;
 
       d3dContext->CopySubresourceRegion1(pBackbuffer,
                                    0,
                                    0,
                                    0,
                                    0,
                                    pPrevBackbuffer,
                                    0,
                                    &intersectBox,
                                    0
                                    );
}

// Render additional content to the current pBackbuffer and call Present1.

如果在应用程序中使用此代码片段,应用随后将准备好调用 IDXGISwapChain1::P resent1,以使用当前脏矩形更新当前帧。

跟踪 N 脏矩形之间的交集

如果指定多个脏矩形(其中可以包含每个帧中新显示的滚动线的脏矩形),则需要验证并跟踪上一帧的所有脏矩形与当前帧的所有脏矩形之间可能发生的任何重叠。 若要计算上一帧的脏矩形与当前帧的脏矩形之间的交集,可以将脏矩形分组为多个区域。

在此代码片段中,我们调用 GDI SetRectRgn 函数,将每个脏矩形转换为矩形区域,然后调用 GDI CombineRgn 函数将所有脏矩形区域合并为一个组。

HRGN hDirtyRgnPrev, hDirtyRgnCurrent, hRectRgn; // Handles to regions 
// Save all the dirty rectangles from the previous frame.
 
RECT dirtyRect[N]; // N is the number of dirty rectangles in current frame, which includes newly scrolled area.
 
int iReturn;
SetRectRgn(hDirtyRgnCurrent, 
       dirtyRect[0].left, 
       dirtyRect[0].top, 
       dirtyRect[0].right, 
       dirtyRect[0].bottom 
       );

for (int i = 1; i<N; i++)
{
   SetRectRgn(hRectRgn, 
          dirtyRect[0].left, 
          dirtyRect[0].top, 
          dirtyRect[0].right, 
          dirtyRect[0].bottom 
          );

   iReturn = CombineRgn(hDirtyRgnCurrent,
                        hDirtyRgnCurrent,
                        hRectRgn,
                        RGN_OR
                        );
   // Handle the error that CombineRgn returns for iReturn.
}

现在,可以使用 GDI CombineRgn 函数来确定上一帧的脏区域与当前帧脏区域之间的交集。 获取相交区域后,调用 GDI GetRegionData 函数从相交区域获取每个单独的矩形,然后调用 ID3D11DeviceContext1::CopySubresourceRegion1 方法,将每个相交矩形复制到当前后台缓冲区中。 下一个代码片段演示如何使用这些 GDI 和 Direct3D 函数。

HRGN hIntersectRgn;
bool bRegionsIntersect;
iReturn = CombineRgn(hIntersectRgn, hDirtyRgnCurrent, hDirtyRgnPrev, RGN_AND);
if (iReturn == ERROR)
{
       // Handle error.
}
else if(iReturn == NULLREGION)
{
       bRegionsIntersect = false;
}
else
{
       bRegionsIntersect = true;
}
 
if (bRegionsIntersect)
{
       int rgnDataSize = GetRegionData(hIntersectRgn, 0, NULL);
       if (rgnDataSize)
       {
              char pMem[] = new char[size];
              RGNDATA* pRgnData = reinterpret_cast<RGNDATA*>(pMem);
              iReturn = GetRegionData(hIntersectRgn, rgnDataSize, pRgnData);
              // Handle iReturn failure.
 
              for (int rectcount = 0; rectcount < pRgnData->rdh.nCount; ++r)
              {
                     const RECT* pIntersectRect = reinterpret_cast<RECT*>(pRgnData->Buffer) +                                            
                                                  rectcount;                
                     D3D11_BOX intersectBox;
                     intersectBox.left    = pIntersectRect->left;
                     intersectBox.top     = pIntersectRect->top;
                     intersectBox.front   = 0;
                     intersectBox.right   = pIntersectRect->right;
                     intersectBox.bottom  = pIntersectRect->bottom;
                     intersectBox.back    = 1;
 
                     d3dContext->CopySubresourceRegion1(pBackbuffer,
                                                      0,
                                                      0,
                                                      0,
                                                      0,
                                                      pPrevBackbuffer,
                                                      0,
                                                      &intersectBox,
                                                      0
                                                      );
              }

              delete [] pMem;
       }
}

带脏矩形的 Bitblt 模型交换链

可以将脏矩形与 DXGI 交换链配合使用,这些交换链在 bitblt 模型中运行, (使用 DXGI_SWAP_EFFECT_SEQUENTIAL) 设置。 使用多个缓冲区的 Bitblt 模型交换链还必须跟踪跨帧重叠脏矩形,其方式与跟踪脏矩形和跨多个帧滚动矩形进行翻转模型交换链中所述的相同。 只有一个缓冲区的 Bitblt 模型交换链不需要跟踪重叠脏矩形,因为整个缓冲区将重新绘制每个帧。

DXGI 1.2 改进