DirectX Graphics Infrastructure (DXGI) :最佳做法

Microsoft DirectX 图形基础结构 (DXGI) 是随 Windows Vista 引入的新子系统,它封装了 Direct3D 10、10.1、11 和 11.1 所需的一些低级别任务。 从 Direct3D 9 程序员的角度来看,DXGI 包含之前打包到 Direct3D 9 API 中的枚举、交换链创建和演示的大部分代码。 将应用移植到 DXGI 和 Direct3D 10.x 和 Direct3D 11.x 时,需要考虑一些注意事项,以确保进程顺利运行。

本文讨论关键移植问题。

Full-Screen问题

在从 Direct3D 9 移植到 DXGI 和 Direct3D 10.x 或 Direct3D 11.x 时,与从窗口模式迁移到全屏模式相关的问题通常会让开发人员感到头疼。 之所以出现main问题,是因为与 DXGI 应用程序不同,Direct3D 9 应用程序需要更动手方法来跟踪窗口样式和窗口状态。 将模式更改代码移植为在 DXGI 上运行时,这通常会导致意外行为。

通常,Direct3D 9 应用程序通过设置前缓冲区的分辨率、强制设备进入全屏独占模式,然后将后台缓冲区分辨率设置为匹配来处理到全屏模式的转换。 对窗口大小的更改使用了单独的路径,因为每当应用程序收到WM_SIZE消息时,都必须从窗口进程中管理这些更改。

DXGI 尝试通过合并这两种情况来简化此方法。 例如,在窗口化模式下拖动窗口边框时,应用程序会收到WM_SIZE消息。 DXGI 截获此消息,并自动调整前缓冲区的大小。 应用程序只需调用 IDXGISwapChain::ResizeBuffers ,将后台缓冲区的大小调整为WM_SIZE中作为参数传递的大小。 同样,当应用程序需要在全屏模式和窗口模式之间切换时,应用程序只需调用 IDXGISwapChain::SetFullscreenState。 DXGI 调整前缓冲区的大小以匹配新选择的全屏模式,并向应用程序发送WM_SIZE消息。 应用程序再次调用 ResizeBuffers,就像拖动窗口边框时一样。

上述说明的方法遵循一个非常特殊的路径。 默认情况下,DXGI 将全屏分辨率设置为桌面分辨率。 但是,许多应用程序会切换到首选的全屏分辨率。 在这种情况下,DXGI 提供 IDXGISwapChain::ResizeTarget。 应在调用 SetFullscreenState 之前调用它。 尽管可以先 (SetFullscreenState (然后是 ResizeTarget) )以相反的顺序调用这些方法,但这样做会导致向应用程序发送额外的WM_SIZE消息。 (这样做也会导致闪烁,因为 DXGI 可能被迫执行两个模式更改。) 调用 SetFullscreenState 后,建议使用 refreshRate 成员DXGI_MODE_DESC清零再次调用 ResizeTarget。这相当于 DXGI 中的无操作指令,但它可以避免刷新率问题,稍后将对此进行讨论。

在全屏模式下,桌面窗口管理器 (DWM) 处于禁用状态。 DXGI 可以执行翻转来显示后台缓冲区内容,而不是在窗口模式下执行 blit 操作。 但是,如果不满足某些要求,则可以撤消这种性能提升。 若要确保 DXGI 执行翻转而不是 blit,前缓冲区和后缓冲区的大小必须相同。 如果应用程序正确处理其WM_SIZE消息,则这应该不会造成问题。 此外,格式必须相同。

大多数应用程序的问题是刷新率。 在调用 ResizeTarget 中指定的刷新速率必须是交换链正在使用的 IDXGIOutput 对象枚举的刷新速率。 如果应用程序将传递到 ResizeTargetDXGI_MODE_DESCRefreshRate 成员归零,DXGI 可以自动计算此值。 请务必不要假设某些刷新率始终受支持,而只是对值进行硬编码。 通常,开发人员选择 60 Hz 作为刷新速率,但不知道监视器中枚举的刷新率大约为监视器的 60,000/1,001 Hz。 如果刷新速率与预期的刷新速率 60 不匹配,DXGI 将强制在全屏模式下执行 blit,而不是翻转。

开发人员经常面临的最后一个问题是如何更改全屏分辨率,同时保持全屏模式。 调用 ResizeTargetSetFullscreenState 有时会成功,但全屏分辨率仍为桌面分辨率。 此外,开发人员可以创建全屏交换链并提供特定的分辨率,但无论传入的数字如何,DXGI 默认为桌面分辨率。 除非另有说明,否则 DXGI 默认为全屏交换链的桌面分辨率。 创建全屏交换链时,必须将 DXGI_SWAP_CHAIN_DESC 结构的 Flags 成员设置为 DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH以替代 DXGI 的默认行为。 此标志还可以传递给 ResizeTarget 以动态启用或禁用此功能。

多个监视器

将 DXGI 与多个监视器一起使用时,需要遵循两个规则。

第一条规则适用于在多个监视器上创建两个或多个全屏交换链。 创建此类交换链时,最好创建窗口化的所有交换链,然后将其设置为全屏。 如果在全屏模式下创建交换链,则创建第二个交换链会导致模式更改发送到第一个交换链,这可能会导致全屏模式终止。

第二个规则适用于输出。 注意创建交换链时使用的输出。 使用 DXGI 时, IDXGIOutput 对象控制在全屏显示时用于监视交换链所使用的对象。 与 DXGI 不同,Direct3D 9 没有输出的概念。

窗口样式和 DXGI

在全屏模式和窗口模式之间切换时,Direct3D 9 应用程序需要执行大量工作。 大部分工作涉及更改窗口样式以添加和删除边框、添加滚动条等。 将应用程序移植到 DXGI 和 Direct3D 10.x 或 Direct3D 11.x 时,此代码通常保留到位。 根据所做的更改,在模式之间切换可能会导致意外行为。 例如,切换到窗口模式时,尽管有专门设置这些样式的代码,但应用程序可能不再具有窗口框架或窗口边框。 发生这种情况是因为 DXGI 现在会自行处理此样式的大部分更改。 手动设置窗口样式可能会干扰 DXGI,这可能会导致意外行为。

建议的行为是尽可能少地执行工作,让 DXGI 处理大部分与窗口的交互。 但是,如果应用程序需要处理其自己的开窗行为,则可以使用 IDXGIFactory::MakeWindowAssociation 告知 DXGI 禁用其某些自动窗口处理。

多线程处理和 DXGI

在多线程应用程序中使用 DXGI 时需要格外小心,以确保不会发生死锁。 由于 DXGI 与开窗的密切交互,它偶尔会向关联的应用程序窗口发送窗口消息。 DXGI 需要先进行开窗更改,然后才能继续,因此它将使用 SendMessage,这是一个同步调用。 应用程序必须在 SendMessage 返回之前处理窗口消息。

在 DXGI 调用和消息泵位于同一线程 (或单线程应用程序) 的应用程序中,几乎不需要执行任何操作。 当 DXGI 调用与消息泵位于同一线程上时, SendMessage 将调用窗口的 WindowProc。 这会绕过消息泵,并允许在调用 SendMessage 后继续执行。 请记住, IDXGISwapChain 调用(如 IDXGISwapChain::P resent)也被视为 DXGI 调用;DXGI 可能会延迟 ResizeBuffersResizeTarget 的工作,直到调用 Present

如果 DXGI 调用和消息泵位于不同的线程上,则必须小心避免死锁。 当消息泵和 SendMessage 位于不同的线程上时, SendMessage 会将消息添加到窗口的消息队列,并等待窗口处理该消息。 如果窗口过程正忙或消息泵未调用,则消息可能永远不会得到处理,DXGI 将无限期等待。

例如,如果应用程序在一个线程上具有其消息泵,在另一个线程上具有其呈现,则它可能需要更改模式。 消息泵线程告知呈现线程更改模式,并等待模式更改完成。 但是,呈现线程调用 DXGI 函数,后者又调用 SendMessage,后者会阻塞,直到消息泵处理消息。 发生死锁是因为两个线程现在被阻止,并且正在相互等待。 为避免这种情况,切勿阻止消息泵。 如果块不可避免,则所有 DXGI 交互应与消息泵位于同一线程上。

Gamma 和 DXGI

尽管使用 SRGB 纹理在 Direct3D 10.x 或 Direct3D 11.x 中可以最好地处理伽玛,但伽玛渐变仍对想要与 2.2 不同的伽玛值或使用不支持 SRGB 的呈现目标格式的开发人员非常有用。 在通过 DXGI 设置伽玛渐变时,请注意两个问题。 第一个问题是传入 IDXGIOutput::SetGammaControl 的渐变值是浮点值,而不是 WORD 值。 此外,请确保从 Direct3D 9 移植的代码在将这些值传递给 SetGammaControl 之前不会尝试转换为 WORD 值。

第二个问题是,更改为全屏模式后, SetGammaControl 可能看起来不起作用,具体取决于正在使用的 IDXGIOutput 对象。 更改为全屏模式时,DXGI 会创建一个新的输出对象,并将 对象用于对输出执行的所有后续操作。 如果在全屏模式切换之前枚举的输出上调用 SetGammaControl ,则调用不会定向到 DXGI 当前正在使用的输出。 若要避免此问题,请调用 IDXGISwapChain::GetContainingOutput 获取当前输出,然后从此输出中调用 SetGammaControl 以获取正确的行为。

有关使用伽玛校正的信息,请参阅 使用伽马校正

DXGI 1.1

Windows 7 中包括并安装到 Windows Vista 上的 Direct3D 11 运行时包含 1.1 版 DXGI。 此更新添加了许多新格式的定义, (特别是 BGRA、10 位 X2 偏差和 Direct3D 11 的 BC6H 和 BC7 纹理压缩) ,以及新版 DXGI 工厂和适配器接口 (CreateDXGIFactory1IDXGIFactory1IDXGIAdapter1) 枚举远程桌面连接。

使用 Direct3D 11 时,当使用 NULL IDXGIAdapter 指针调用 D3D11CreateDeviceD3D11CreateDeviceAndSwapChain 时,运行时将默认使用 DXGI 1.1。 不支持在同一进程中混合使用 DXGI 1.0 和 DXGI 1.1。 也不支持在同一进程中混合来自不同工厂的 DXGI 对象实例。 因此,使用 DirectX 11 时,对 DXGI 接口的任何显式使用都会使用由“DXGI.DLL”中的 CreateDXGIFactory1 入口点创建的 IDXGIFactory1,以确保应用程序始终使用 DXGI 1.1。

DXGI 1.2

包含在 Windows 8 中的 Direct3D 11.1 运行时还包括 DXGI 版本 1.2。

DXGI 1.2 支持以下功能:

  • 立体声渲染

  • 每像素 16 位格式

    • 现在完全支持DXGI_FORMAT_B5G6R5_UNORM和DXGI_FORMAT_B5G5R5A1_UNORM
    • 添加了新的DXGI_FORMAT_B5G5R5A1_UNORM格式
  • 视频格式

  • 新的 DXGI 接口

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