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

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 交换链使用滚动。 可以将脏矩形与在翻转模型和 bitblt 模型中运行的 DXGI 交换链配合使用(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 成员指定。 填充矩形显示应用使用新内容更新的脏矩形。 填充的矩形由 DirtyRectsCountpDirtyRectsDXGI_PRESENT_PARAMETERS的成员指定。

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

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

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

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

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

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

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

  1. 在框架 1 中呈现脏矩形。
  2. 从框架 1 复制脏矩形与框架 2 中的脏矩形之间的交集到框架 2 的脏矩形中。
  3. 在框架 2 中呈现脏矩形。

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

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

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

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

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

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

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

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

在此代码片段中,调用 IntersectRect 返回另一个 RECT(称为 dirtyRectCopy)中的两个脏矩形的交集。 代码片段确定两个脏矩形相交后,它将调用 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 模型交换链

可以将脏矩形与以 bitblt 模型运行的 DXGI 交换链配合使用(用 DXGI_SWAP_EFFECT_SEQUENTIAL设置)。 使用多个缓冲区的 Bitblt 模型交换链还必须跟踪跨帧重叠的脏矩形,就像 跟踪脏矩形和跨多个帧滚动矩形 用于翻转模型交换链的相同方式。 仅包含一个缓冲区的 Bitblt 模型交换链不需要跟踪重叠的脏矩形,因为整个缓冲区将重新绘制每个帧。

DXGI 1.2 改进