DXGI 概述

Microsoft DirectX 图形基础结构 (DXGI) 识别到某些图形部分的演变速度比其他部分慢。 DXGI 的主要目标是管理可以独立于 DirectX 图形运行时的低级别任务。 DXGI 为将来的图形组件提供通用框架;利用 DXGI 的第一个组件是 Microsoft Direct3D 10。

在早期版本的 Direct3D 中,Direct3D 运行时中包含低级别任务,例如硬件设备的枚举、向输出呈现的帧、控制 gamma 以及管理全屏切换。 这些任务现在在 DXGI 中实现。

DXGI 的目的是与内核模式驱动程序和系统硬件通信,如下图所示。

应用程序、dxgi、驱动程序和硬件之间的通信示意图

应用程序可以直接访问 DXGI,也可以在 D3D11_1.h、D3D11.h、D3D10_1.h 或 D3D10.h 中调用 Direct3D API,以处理与 DXGI 的通信。 如果应用程序需要枚举设备或控制如何将数据呈现给输出,则可能需要直接使用 DXGI。

本主题包含以下各节:

若要查看 Direct3D 11 硬件支持的格式,请执行以下操作:

枚举适配器

适配器是计算机的硬件和软件功能的抽象。 计算机上通常有许多适配器。 某些设备在硬件 ((如视频卡) )中实现,有些设备在软件 ((如 Direct3D 参考光栅器) )中实现。 适配器实现图形应用程序使用的功能。 下图显示了一个系统,其中包含一台计算机、两个适配器 (视频卡) 和三个输出监视器。

具有两个视频卡和三个监视器的计算机示意图

枚举这些硬件时,DXGI 会为每个输出 (或监视) 创建 IDXGIOutput1 接口,为每个视频卡 (创建 IDXGIAdapter2 接口,即使它是内置于主板) 的视频卡。 枚举通过使用 IDXGIFactory 接口调用 IDXGIFactory::EnumAdapters 返回一组表示视频硬件的 IDXGIAdapter 接口来完成。

DXGI 1.1 添加了 IDXGIFactory1 接口。 IDXGIFactory1::EnumAdapters1 返回一组表示视频硬件的 IDXGIAdapter1 接口。

如果要在使用 Direct3D API 时选择特定的视频硬件功能,建议使用每个适配器句柄和可能的硬件功能级别迭代调用 D3D11CreateDeviceD3D11CreateDeviceAndSwapChain 函数。 如果指定适配器支持功能级别,则此函数成功。

有关枚举 Windows 8 适配器的新信息

从 Windows 8 开始,始终存在名为“Microsoft Basic Render Driver”的适配器。 此适配器的 VendorId 为 0x1414 ,DeviceID 为 0x8c。 此适配器还在其 DXGI_ADAPTER_DESC2 结构的 Flags 成员中设置了 DXGI_ADAPTER_FLAG_SOFTWARE 值。 此适配器是无显示输出的仅限呈现的设备。 DXGI 永远不会为此适配器返回 DXGI_ERROR_DEVICE_REMOVED

如果计算机的显示驱动程序不起作用或已禁用,则计算机的主 (NULL) 适配器可能也称为“Microsoft Basic Render Driver”。但此适配器具有输出,并且未设置 DXGI_ADAPTER_FLAG_SOFTWARE 值。 操作系统和应用默认使用此适配器。 如果安装或启用显示驱动程序,应用可以接收此适配器 的DXGI_ERROR_DEVICE_REMOVED ,然后必须再次重新枚举适配器。

当计算机的主显示适配器是“Microsoft Basic Display Adapter” (WARP 适配器) 时,该计算机还有另一个适配器。 第二个适配器是无显示输出且 DXGI 永远不会返回 DXGI_ERROR_DEVICE_REMOVED的仅限呈现的设备。

如果要将 WARP 用于呈现、计算或其他长时间运行的任务,建议使用仅呈现设备。 可以通过调用 IDXGIFactory1::EnumAdapters1 方法获取指向仅呈现设备的指针。 在 D3D11CreateDeviceDriverType 参数中指定D3D_DRIVER_TYPE_WARP时,还会创建仅呈现设备,因为 WARP 设备也使用仅限呈现的 WARP 适配器。

呈现

应用程序的工作是呈现帧,并要求 DXGI 向输出显示这些帧。 如果应用程序有两个可用缓冲区,它可以呈现一个缓冲区,同时呈现另一个缓冲区。 应用程序可能需要两个以上的缓冲区,具体取决于呈现帧所需的时间或呈现所需的帧速率。 创建的缓冲区集称为交换链,如下所示。

交换链的插图

交换链有一个前缓冲区和一个或多个后台缓冲区。 每个应用程序都创建自己的交换链。 为了最大限度地提高将数据呈现到输出的速度,几乎总是在显示子系统的内存中创建交换链,如下图所示。

显示子系统的插图

显示子系统 (通常是视频卡,但可以在主板上实现,) 包含 GPU、数模转换器 (DAC) 和内存。 交换链在此内存中分配,使演示速度非常快。 显示子系统将前端缓冲区中的数据呈现给输出。

交换链设置为在全屏或窗口模式中绘图,这无需知道输出是开窗还是全屏。 全屏模式交换链可以通过切换显示分辨率来优化性能。

创建交换链

Direct3D 9 和 Direct3D 10 之间的差异:Direct3D 10 是第一个使用 DXGI 的图形组件。 DXGI 具有一些不同的交换链行为。
  • 在 DXGI 中,交换链在创建交换链时绑定到窗口。 此更改可提高性能并节省内存。 以前版本的 Direct3D 允许交换链更改交换链所绑定到的窗口。
  • 在 DXGI 中,交换链在创建时绑定到呈现设备。 Direct3D 创建设备函数返回的设备对象实现 IUnknown 接口。 可以调用 QueryInterface 来查询设备的相应 IDXGIDevice2 接口。 对呈现设备的更改需要重新创建交换链。
  • 在 DXGI 中,可用的交换效果是DXGI_SWAP_EFFECT_DISCARD和DXGI_SWAP_EFFECT_SEQUENTIAL。 从Windows 8 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL交换效果也可用。 下表显示了 Direct3D 9 到 DXGI 交换效果定义的映射。

    D3D9 交换效果 DXGI 交换效果
    D3DSWAPEFFECT_DISCARD DXGI_SWAP_EFFECT_DISCARD
    D3DSWAPEFFECT_COPY 具有 1 个缓冲区的DXGI_SWAP_EFFECT_SEQUENTIAL
    D3DSWAPEFFECT_FLIP 具有 2 个或更多缓冲区的DXGI_SWAP_EFFECT_SEQUENTIAL
    D3DSWAPEFFECT_FLIPEX 具有 2 个或更多缓冲区的DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL

交换链的缓冲区以特定大小和特定格式创建。 应用程序 (指定这些值,或者可以在启动时从目标窗口) 继承大小,然后可以选择在窗口大小更改以响应用户输入或程序事件时对其进行修改。

创建交换链后,通常需要在其中呈现图像。 下面是一个代码片段,用于设置要呈现到交换链中的 Direct3D 上下文。 此代码从交换链中提取缓冲区,从该缓冲区创建呈现目标视图,然后在设备上设置它:

ID3D11Resource * pBB;
ThrowFailure( pSwapChain->GetBuffer(0, __uuidof(pBB),    
  reinterpret_cast<void**>(&pBB)), "Couldn't get back buffer");
ID3D11RenderTargetView * pView;
ThrowFailure( pD3D11Device->CreateRenderTargetView(pBB, NULL, &pView), 
  "Couldn't create view" );
pD3D11DeviceContext->OMSetRenderTargets(1, &pView, 0);
        

应用程序将帧呈现到交换链缓冲区后,调用 IDXGISwapChain1::P resent1。 然后,应用程序可以呈现下一个图像。

交换链的护理和馈送

呈现图像后,调用 IDXGISwapChain1::P resent1 并呈现下一个图像。 这就是你的责任范围。

如果之前调用 IDXGIFactory::MakeWindowAssociation,用户可以按Alt-Enter组合键,DXGI 将在窗口模式和全屏模式之间切换应用程序。 建议使用 IDXGIFactory::MakeWindowAssociation,因为非常需要用户的标准控制机制。

虽然无需编写比所述更多的代码,但几个简单的步骤可以使应用程序更具响应能力。 最重要的考虑因素是调整交换链缓冲区的大小,以响应输出窗口的大小调整。 当然,应用程序的最佳路由是响应WM_SIZE,并调用 IDXGISwapChain::ResizeBuffers,传递消息参数中包含的大小。 此行为显然会使应用程序在用户拖动窗口边框时对用户做出良好响应,但也正是它实现了流畅的全屏切换。 每当发生此类转换时,窗口都将收到WM_SIZE消息,调用 IDXGISwapChain::ResizeBuffers 是交换链重新分配缓冲区存储以实现最佳呈现的机会。 这就是为什么应用程序在调用 IDXGISwapChain::ResizeBuffers 之前需要释放对现有缓冲区的任何引用的原因。

如果无法调用 IDXGISwapChain::ResizeBuffers 来响应切换到全屏模式, (最自然地响应WM_SIZE) ,可能会阻止翻转的优化,其中 DXGI 可以简单地交换正在显示的缓冲区,而不是复制全屏的数据。

IDXGISwapChain1::P resent1 将通知你是否通过 DXGI_STATUS_OCCLUDED完全遮挡输出窗口。 发生这种情况时,我们建议应用程序使用 DXGI_PRESENT_TEST) 调用 IDXGISwapChain1::P resent1 进入待机 模式 (, 因为用于呈现帧的资源会浪费。 使用DXGI_PRESENT_TEST可防止在仍执行遮挡检查时显示任何数据。 IDXGISwapChain1::P resent1 返回S_OK后,应退出待机模式;请勿使用返回代码切换到待机模式,因为这样做会使交换链无法放弃全屏模式。

Direct3D 11.1 运行时从 Windows 8 开始提供,它提供翻转模型交换链 (即在 DXGI_SWAP_CHAIN_DESCDXGI_SWAP_CHAIN_DESC1) SwapEffect 成员中设置了DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL值的交换链。 使用翻转模型交换链向输出显示帧时,DXGI 会从所有管道状态位置(如输出合并呈现目标)取消绑定后台缓冲区,该写入到后台缓冲区 0。 因此,建议在呈现到后台缓冲区之前立即调用 ID3D11DeviceContext::OMSetRenderTargets 。 例如,不要调用 OMSetRenderTargets ,然后执行最终不会呈现到资源的计算着色器工作。 有关翻转模型交换链及其优点的详细信息,请参阅 DXGI 翻转模型

注意

在 Direct3D 10 和 Direct3D 11 中,调用 IDXGISwapChain1::P resent1 后,无需调用 IDXGISwapChain: :GetBuffer 即可检索回退缓冲区 0,因为为方便起见,后台缓冲区的标识发生了更改。 这在 Direct3D 12 中不会发生,应用程序必须改为手动跟踪缓冲区索引。

处理窗口大小调整

可以使用 IDXGISwapChain::ResizeBuffers 方法处理窗口大小调整。 在调用 ResizeBuffers 之前,必须释放对交换链缓冲区的所有未完成引用。 通常保存对交换链缓冲区的引用的对象是 render-target-view。

以下示例代码演示如何从 WindowProc 处理程序中为WM_SIZE消息调用 ResizeBuffers

    case WM_SIZE:
        if (g_pSwapChain)
        {
            g_pd3dDeviceContext->OMSetRenderTargets(0, 0, 0);

            // Release all outstanding references to the swap chain's buffers.
            g_pRenderTargetView->Release();

            HRESULT hr;
            // Preserve the existing buffer count and format.
            // Automatically choose the width and height to match the client rect for HWNDs.
            hr = g_pSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
                                            
            // Perform error handling here!

            // Get buffer and create a render-target-view.
            ID3D11Texture2D* pBuffer;
            hr = g_pSwapChain->GetBuffer(0, __uuidof( ID3D11Texture2D),
                                         (void**) &pBuffer );
            // Perform error handling here!

            hr = g_pd3dDevice->CreateRenderTargetView(pBuffer, NULL,
                                                     &g_pRenderTargetView);
            // Perform error handling here!
            pBuffer->Release();

            g_pd3dDeviceContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL );

            // Set up the viewport.
            D3D11_VIEWPORT vp;
            vp.Width = width;
            vp.Height = height;
            vp.MinDepth = 0.0f;
            vp.MaxDepth = 1.0f;
            vp.TopLeftX = 0;
            vp.TopLeftY = 0;
            g_pd3dDeviceContext->RSSetViewports( 1, &vp );
        }
        return 1;

选择 DXGI 输出和大小

默认情况下,DXGI 选择包含窗口大部分工作区的输出。 这是 DXGI 在响应 alt-enter 时全屏显示时唯一可用的选项。 如果应用程序选择自行转到全屏模式,则可以调用 IDXGISwapChain::SetFullscreenState ,并传递显式 IDXGIOutput1 (或 NULL(如果应用程序愿意让 DXGI 决定) )。

若要在全屏或开窗时调整输出大小,建议调用 IDXGISwapChain::ResizeTarget,因为此方法也会调整目标窗口的大小。 由于目标窗口已调整大小,因此操作系统会发送 WM_SIZE,并且代码自然会调用 IDXGISwapChain::ResizeBuffers 作为响应。 因此,调整缓冲区的大小,然后调整目标大小是浪费的。

在全屏模式下调试

DXGI 交换链仅在绝对必要时才放弃全屏模式。 这意味着,只要调试窗口不与交换链的目标窗口重叠,就可以使用多个监视器调试全屏应用程序。 或者,可以通过不设置 DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 标志来完全阻止模式切换。

如果允许模式切换,则每当输出窗口被另一个窗口遮挡时,交换链就会放弃全屏模式。 封闭检查是在 IDXGISwapChain1::P resent1 期间执行的,或者由单独的线程执行,该线程的目的是watch,以查看应用程序是否在 (变得无响应,并且不再调用 IDXGISwapChain1::P resent1) 。 若要禁用单独线程导致切换的功能,请将以下注册表项设置为任何非零值。

HKCU\Software\Microsoft\DXGI\DisableFullscreenWatchdog

销毁交换链

不能在全屏模式下释放交换链,因为这样做可能会创建线程争用 (这将导致 DXGI) 引发不连续异常。 在释放交换链之前,首先使用 IDXGISwapChain::SetFullscreenState ( FALSENULL ) ) 切换到窗口模式 (,然后调用 IUnknown::Release

使用旋转的监视器

应用程序无需担心监视器方向,DXGI 将在演示期间轮换交换链缓冲区(如有必要)。 当然,这种额外的旋转可能会影响性能。 为了获得最佳性能,请执行下列操作,在应用程序中处理轮换:

  • 使用 DXGI_SWAP_CHAIN_FLAG_NONPREROTATED。 这会通知 DXGI 应用程序将 (生成旋转图像,例如,通过更改其投影矩阵) 。 需要注意的一点是,此标志仅在全屏模式下有效。
  • 按其旋转大小分配每个交换链缓冲区。 如有必要,请使用 IDXGIOutput::GetDesc 获取这些值。

通过在应用程序中执行轮换,DXGI 将只执行复制而不是复制和旋转。

Direct3D 11.1 运行时从 Windows 8 开始提供,它提供翻转模型交换链 (即在 DXGI_SWAP_CHAIN_DESC1) SwapEffect 成员中设置了DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL值的交换链。 为了最大化翻转模型交换链提供的演示优化,我们建议使应用程序定向内容,以匹配内容完全占用输出时内容所在的特定输出。 有关翻转模型交换链及其优点的详细信息,请参阅 DXGI 翻转模型

切换模式

进行全屏切换时,DXGI 交换链可能会更改输出的显示模式。 若要启用自动显示模式更改,必须在交换链说明中指定 DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH 。 如果显示模式自动更改,DXGI 将选择最适度的模式, (大小和分辨率不会更改,但颜色深度可能会) 。 调整交换链缓冲区的大小不会导致模式切换。 交换链会隐式承诺,即如果选择与目标输出支持的显示模式完全匹配的后台缓冲区,则在该输出上进入全屏模式时,它将切换到该显示模式。 因此,可以通过选择后台缓冲区大小和格式来选择显示模式。

全屏性能提示

在全屏应用程序上调用 IDXGISwapChain1::P resent1 时,交换链会翻转 (而不是 blits,) 后台缓冲区的内容到前缓冲区。 这要求使用 DXGI_SWAP_CHAIN_DESC1 ) 中指定的枚举 显示模式 (创建交换链。 如果无法枚举显示模式,或在说明中错误地指定显示模式,交换链可能会改为 (bitblt) 执行位块传输。 bitblt 会导致额外的拉伸副本以及一些视频内存使用量增加,并且难以检测。 若要避免此问题,请在创建交换链之前枚举显示模式并正确初始化交换链说明。 这将确保在全屏模式下翻转时获得最佳性能,并避免额外的内存开销。

多线程注意事项

在具有多个线程的应用程序中使用 DXGI 时,需要小心避免造成两个不同的线程相互等待完成的死锁。 有两种情况可能会发生这种情况。

  • 呈现线程不是消息泵线程。
  • 执行 DXGI API 的线程与创建窗口的线程不同。

请注意,使用全屏交换链时,永远不会让消息泵线程在呈现线程上等待。 例如,从呈现线程 ) 调用 IDXGISwapChain1::P resent1 (可能会导致呈现线程等待消息泵线程。 发生模式更改时,如果 Present1 调用 ::SetWindowPos () 或 ::SetWindowStyle () ,并且其中任一方法调用 ::SendMessage () ,则可能会出现此方案。 在这种情况下,如果消息泵线程有一个关键部分保护它,或者如果呈现线程被阻止,则两个线程将死锁。

有关将 DXGI 用于多个线程的详细信息,请参阅 多线程和 DXGI

来自 DLLMain 的 DXGI 响应

由于 DllMain 函数无法保证其加载和卸载 DLL 的顺序,因此建议应用的 DllMain 函数不要调用 Direct3D 或 DXGI 函数或方法,包括创建或释放对象的函数或方法。 如果应用的 DllMain 函数调用特定组件,该组件可能会调用操作系统上不存在的另一个 DLL,从而导致操作系统崩溃。 Direct3D 和 DXGI 可能会加载一组 DLL(通常是一组驱动程序),这些 DLL 因计算机而异。 因此,即使应用在 DllMain 函数调用 Direct3D 或 DXGI 函数或方法时不会在开发和测试计算机上崩溃,也可能在另一台计算机上运行时崩溃。

为了防止你创建可能导致操作系统崩溃的应用,DXGI 在指定情况下提供以下响应:

  • 如果应用的 DllMain 函数释放了对 DXGI 工厂的最后一个引用,DXGI 将引发异常。
  • 如果应用的 DllMain 函数创建 DXGI 工厂,DXGI 将返回错误代码。

DXGI 1.1 更改

我们在 DXGI 1.1 中添加了以下功能。

DXGI 1.2 更改

我们在 DXGI 1.2 中添加了以下功能。

  • 立体声交换链
  • 翻转模型交换链
  • 优化的演示文稿 (滚动、脏矩形和旋转)
  • 改进了共享资源和同步
  • 桌面复制
  • 优化了视频内存的使用
  • 支持每像素 16 位 (bpp) 格式 (DXGI_FORMAT_B5G6R5_UNORM、DXGI_FORMAT_B5G5R5A1_UNORM、DXGI_FORMAT_B4G4R4A4_UNORM)
  • 调试 API

有关 DXGI 1.2 的详细信息,请参阅 DXGI 1.2 改进

DXGI 编程指南