DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

时间:2022-05-17 22:59:13

今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.microsoft.com/en-us/library/windows/desktop/ee417025(v=vs.85).aspx

我本意是记录下学习笔记,但可能写成了翻译,但这也没有办法的事,MSDN的写作风格就是简单凝练,缺少参考索引,所以看MSDN往往也就是读完正文,点点加有超链接的名词,顶多再跑跑例程而已。

Microsoft DirectX Graphics Infrastructure(DXGI)是从WIndows Vista时代开始引入的一个子系统,它封装了一些Direct3D 10, 10.1, 11和11.1需要的低层次(low-level)任务。对于使用Direct3D 9的程序员,DXGI包括绝大部分之前打包进Direct3D 9 的enumeration,生成swap-chain和presentation相关API。当你要把应用迁移到DXGI,Direct3D 10.x和Direct3D 11.x,你必须充分考察一些事项以保证程序正常运行。

这篇文章主要讨论以下事项:

  • 全屏相关琐事
  • 多显示器
  • 窗口样式和DXGI
  • 多线程和DXGI
  • 伽马(Gamma)和DXGI
  • DXGI 1.1
  • DXGI 1.2

全屏相关琐事

从Direct3D 9迁移到DXGI、Direct3D 10.x或者Direct3D 11.x,会给窗口模式到全屏模式转换的操作带来很多状况。这主要是因为Direct3D 9不像使用DXGI的程序那样需要那么多的手动操作,DXGI给开发者提供了很多可以追踪窗口样式和状态的细节操作。当古老的模式转换程序在新平台运行的时候,总是会引起一些意想不到的问题。

Direct3D 9通常通过设置front buffer分辨率来转换窗口模式到全屏模式,并强迫设备进入全屏独占模式再设置back buffer与其匹配。还有一条单独路径用于处理窗口尺寸,因为它们必须处理窗口进程接收到的WM_SIZE信号。

DXGI结合了两种情况的处理,试图简化处理流程。例如窗口模式下边缘被拉扯时,应用接收到WM_SIZE信号。DXGI拦截该信号,并修改front buffer。开发者只需要针对back buffer调用IDXGISwapChain::ResizeBuffers调整尺寸与随WM_SIZE信号所传入参数一致。当应用需要在全屏及窗口模式间切换时,只需调用IDXGISwapChain:SetFullscreenState。DXGI自动调整front buffer并发送WM_SIZE信号。应用再次调用ResizeBuffers,就像窗口边缘被拖拽时一样。

上述解释的方法论使用了一条独特的路径。DXGI默认设置桌面分辨率为全屏分辨率。但很多应用要为全屏切换到某个特殊分辨率。DXGI为这种情况准备了IDXGISwapChain::ResizeTarget。在调用SetFullscreenState之前调用。虽然可以以相反的顺序调用,但会多发送WM_SIZE一次,课程会引起闪烁。在调用SetFullScreenState之后还是要调用ResizeTarget,只不过参数DXGI_MODE_DESC的RefreshRate子项设为空。这是个非操作指令,但这可以避免刷新率相关的琐事,我们接下来就聊聊这个。

全屏模式时,桌面窗口管理器是禁用的。此时DXGI不会像窗口模式那样用blit,而是使用翻转去显示back buffer。如果特定的需求不满足,效益是不明确的。为了确定DXGI会用翻转代替blit,front buffer和back buffer必须保持一致。如果此前应用正确处理了WM_SIZE,两个buffer格式必然一直,也就没什么好担心的了。

大多数应用的主要问题是刷新率。刷新率必须是swap chain正在使用的IDXGIOutput对象的枚举值之一(IDXGIOutput可以枚举系统支持的所有刷新率)。如果在调用ReisizeTarget的时候,DXGI_MODE_DESC结构的RefreshRate子项置空,就能在调用后在子项中返回由系统指定的数值。注意不要以为某个刷新率会一直被支持而直接把它写在程序里。开发者经常希望获得一个60Hz的刷新率,而不知道系统真实支持刷新率只是60Hz的近似值,如果强行在程序里写入60Hz这个固定数值,那程序肯定会放弃翻转,而改用blit。

最后还要注意个问题,开发者经常要面对的需求是在全屏模式下改变分辨率。有时调用ResizeTarget和SetFullscreenState返回成功,但全屏分辨率依然是桌面分辨率。或者开发者给swap chain指定了一个分辨率,但DXGI却不管这些,还是把全屏分辨率设定为桌面分辨率。除非还调用了其他指令,DXGI就是默认把桌面分辨率设为全屏分辨率。在创建全屏swap chain时DXGI_SWAP_CHAIN_DESC结构的Flags子项一定要设为DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH,以重写DXGI的默认行为。这个flag

也可以在ResizeTarget函数里动态调用。

多显示器

在多显示器情况下,DXGI要遵循两条准则。

第一条应用于再多显示器上创建两个以上全屏swap chain。当创建这种应用的时候,最好的办法是先设置他们的swap chain为窗口模式再设为全屏模式。如果swap chain在创建时被设为全屏模式,那么第二个swap chain会引起第一个swap chain模式转变,有可能会让它退出全屏模式。

第二条应用于输出(DXGI术语里的output往往是指显示器)。当创建swap chain时一定要小心输出设备。在DXGI使用环境中设置全屏时,IDXGIOutput对象控制控制着swap chain使用的显示器。Direct3D 9就没输出设备的概念。

窗口样式和DXGI

Direct3D 9应用在窗口模式与全屏模式切换时有很多工作要做。像改变窗口样式:填减边框、滚动条之类的。当应用迁移到DXGI,Direct3D 10.x或者Direct3D 11.x的时候,这些工作都被安排在适当的位置。因变化的不同,也有可能出现意想不到的情况。例如:当迁移到窗口模式时,应用不再有窗口框架或窗口边框相应处理程序。这是因为DXGI自动化了一部分工作,隐藏了相关过程。可以手动设置DXGI,但也可能引起期望外的状况。

通常我们建议尽量减少人力工作,将工作尽可能交给DXGI。但如果确实需要手动处理窗口的行为属性,可以用IDXGIFactory::MakeWindowAssociation禁掉一部分自动功能。

多线程和DXGI

还得特别注意在多线程编程时使用DXGI不能引起死锁。因为DXGI与窗口化的紧密互动关系,它偶尔会发送窗口信号给相关应用窗口。DXGI需要窗口在继续执行前响应它的信号,它会用SenMessage函数,这是个同步调用。应用必须在SendMessage返回前处理窗口信号。

如果在一个程序里,DXGI调用和消息泵在一个线程里(或者程序本身就是单线程的),这种情况没太多要担心的。这种情况下,SendMessage会调用窗口的WindowProc(创建窗口的时候在描述结构里填写的那个)。这样就绕过了消息泵,并且允许在调用SendMessage后继续执行应用。IDXGISwapChain和IDXGISwapChain::Present都属于DXGI调用,DXGI会因为ResizeBuffers或ResizeTarget推迟工作直到Present被调用。

如果DXGI调用和消息泵不在同一线程,一定要注意避免死锁。当消息泵和SendMessage在不同的线程,SendMessage把信号添加到信号队列,然后等待窗口处理这个信号。如果窗口例程没有被消息泵调用,这个消息可能永远不会被处理并且DXGI要一直等下去了。

例如,一个程序消息泵在一个线程,渲染在另一个,它可能要改变模式。消息泵线程告诉渲染线程要改变模式了,然后等在那里一直等到改变完成。然而,这个渲染县城调用了DXGI函数,它又调用了SendMessage,就一直锁定在这里等待消息泵处理这个信号。一个死锁发生了,因为两个线程在互相等待。为了避免这样,永远都不要锁定消息泵。如果一个锁定无法避免,那么所有DXGI交互都应该与消息泵发生在同一个线程。

伽马(Gamma)和DXGI

虽然在Direct3D 10.x和Direct3D 11.x里,gamma最好用SRGB材质处理,但是对于不想使用2.2这个值或者渲染标的不支持SRGB的程序员来说,gamma校正还是很有用的。用DXGI设置gamma校正的时候一定要注意两个问题:第一校正值传入IDXGIOutput:SetGammaControl是float值,不是WORD值。同样从Direct3D 9迁移来的程序不要试图将传入SetGammaControl的值转换成WORD值。

第二件事,在转换为全屏模式后,因为使用的IDXGIOutput对象的因素,SetGammaControl可能看上去没有效果。当转换为全屏模式后,DXGI创建了一个新的输出对象,并且将这个对象应用于之后的输出相关操作。使用全屏前枚举出来的对象调用SetGammaControl是不对的,当前处在生效状态的对象已经变成其他的对象了。为了避免这种情况要在调用SetGammaControl之前调用IDXGISwapChain::GetContainingOutput来获取当前对象,从而得到正确的行为。

要想获取正确使用Gamma的相关知识,请浏览Using gamma correction

DXGI 1.1

包括在Windows 7和安装在Windows Vista(KB971644)的Direct3D 11运行时包括DXGI 1.1。这个升级包增加了一些新格式的定义(特别是BGRA,10-bit X2 bias和Direct3D 11的BC6H和BC7纹理压缩接口),还有些用于美剧远程桌面链接的DXGI工厂接口适配器(CreateDXGIFactory1, IDXGIFactory1,IDXGIAdapter1)。

当时用Direct3D 11,调用D3D11CreateDevice或者D3D11CreateDeviceAndSwapChain并传入空IDXGIAdapter指针,运行时会默认使用DXGI 1.1。不支持在同一个进程内混用1.0和1.1版本。在同一进程内昏庸DXGI对象实例也被不支持。因此, 当你使用DirectX11时,显示使用DXGI.DLL里的CreateDXFactory1入口点创建IDXGIFactory1的DXGI接口使来保证应用一直使用了1.1版本。

DXGI 1.2

Windows 8里包括Direct3D 11.1运行时同时也包括1.2版本。

1.2版本同时启用了以下这些特点:

  • 立体渲染
  • 16位像素格式

支持DXGI_FORMAT_B5G6R5_UNORM和DXGI_FORMAT_B5G5R5A1_UNORM全部特性

添加了一个新的DXGI_FORMAT_B5G5R5A1_UNORM格式

  • 视频格式
  • 新DXGI接口