本文相关知识点:
D3D12总览
COM
Textures Formats
交换链和页面翻转
深度缓冲

这一章学习什么是Direct3D,以及如何初始化它,为3D绘图做准备。还介绍了基本的Direct3D主题,如表面、像素格式、页面翻转、深度缓冲和多采样还,如何使用性能计数器度量时间,使用它来计算每秒呈现的帧数。还提供了一些调试Direct3D应用程序的技巧。

Direct3D的初始化过程要求熟悉一些基本的Direct3D类型和基本的图形概念;本章的第一节和第二节讨论这些要求。然后详细介绍初始化Direct3D所需的步骤。引入实时图形应用程序所需的精确计时和时间度量。

目的:

  1. 基本了解Direct3D在3D硬件编程中的作用。
  2. 了解COM在Direct3D中扮演的角色。
  3. 学习基本的图形概念,如2D图像如何存储,页面翻转,深度缓冲,多采样,以及CPU和GPU如何交互。
  4. 学习如何使用性能计数器函数获得高分辨率计时器读数。
  5. 了解如何初始化Direct3D。
  6.  熟悉本书所有演示使用的应用程序框架的一般结构。

4.1.1 Direct3D 12 概述

D3D是一个低级图形API,用于控制和编程GPU,用硬件加速渲染。

4.1.2 COM

Component Object Model 简称COM,是一种允许DirectX独立于编程语言并具有向后兼容性的技术。通常将COM对象称为接口,可以将其视为c++类来使用。

4.1.3 纹理格式

一般的2D纹理存储图像的对应像素的颜色值。但是纹理可以有更广泛的用途,一维纹理就像数据元素的一维数组,二维纹理就像数据元素的二维数组,三维纹理就相对于三维数组。GPU可以对其进行特殊操作,比如滤波和多重采样。

注意,纹理不能存储任意类型的数据。只能存储右DXGI_FORMAT枚举类型。

下面有几个例子:

  1. DXGI_FORMAT_R32G32B32_FLOAT: 每个元素有三个32位浮点组件。
  2. DXGI_FORMAT_R16G16B16A16_UNORM: 每个元素有四个映射到[0,1]范围的16位组件。
  3. DXGI_FORMAT_R32G32_UINT: 每个元素都有两个32位无符号整数组件。
  4. DXGI_FORMAT_R8G8B8A8_UNORM: 每个元素有四个映射到[0,1]范围的8位无符号组件。
  5. DXGI_FORMAT_R8G8B8A8_SNORM: 每个元素有四个映射到[- 1,1]范围的8位有符号组件。
  6. DXGI_FORMAT_R8G8B8A8_SINT: 每个元素有四个映射到[- 128,127]范围的8位有符号整数组件。
  7. DXGI_FORMAT_R8G8B8A8_UINT: 每个元素有四个映射到[0,255]范围的8位无符号整数组件。

RGBA分别代表红绿蓝,透明度。虽然这些类型名字是和颜色有关的,但是并不意味着必须存储颜色信息。比如:

DXGI_FORMAT_R32G32B32_FLOAT

是可以存储任何3D向量的。

4.1.4 The Swap China and Page Flipping

交换链和页面翻转

为了避免动画的闪烁,使观察者(玩家)只看到渲染好的画面,并且看不到每个画面渲染的过程,就用到了一个后台缓冲,即每个画面在渲染完成之前是在一个用户看不到的后台进行渲染,整个画面完成后才放到屏幕上。所以,硬件将会维护两个缓冲区,一个为前端,一个为后端。前缓冲区储存当前显示在屏幕上的图像数据,后缓冲区是下一帧的动画。下一帧渲染好后,前后缓冲区角色互换,继续下一帧。整个过程如图:

Front Buffer Ptr 
Back Buffer Ptr 
Frame n 
Buffer A 
Buffer B 
Frame n+l 
Buffer B 
Buffer A 
Frame n+2 
Buffer A 
Buffer B

前后缓冲区形成了交换链。

D3D中交换链是 IDXGISwapChain 接口。该接口存储前后缓冲区的纹理数据,并提供调整缓冲区大小的方法。

IDXGISwapChain::ResizeBuffers

IDXGISwapChain::Present

使用两个缓冲区称为双缓冲。可以使用两个以上的缓冲区,使用三个就是三缓存,一般两个缓冲区就够了。

4.1.5 深度缓冲

深度缓冲是纹理的一个例子。不存储颜色信息,存储的是特定像素的深度信息。与后缓冲区的像素一一对应。深度信息的0.0表示离查看器最近的,1.0表示最远。

如图一个简单的场景,存在遮挡关系,需要让DirectX确定像素的前后关系,就是用了深度缓冲或Z-buffer的技术。使用深度缓冲时,绘制对象的顺序就不重要了。

下图形象展示了深度缓冲的原理:

View Window 
Eye 
Eye 
View Window

再进行任何渲染前,深度缓冲区会被清除为默认值,通常是1.0,即可行的深度最大值。后缓冲区也会重置为默认颜色。对于图中的像素点P,我们一看就知道渲染成P1是正确的,但是计算机不知道。所以,深度缓冲区的存在,可以让计算机知道P1挡住了P2和P3。深度信息会被标准化到0.0到1.0之间。

操作 P d 描述
清除操作 黑色 1.0 初始化像素颜色和对应深度值
画圆柱 P3 d3 由于d3<=d(1.0),所以更新信息,P=P3,d=d3
画球 P1 d1 由于d1<=d(d1),更新,P=P1,d=d1
画圆锥 P1 d1 d2>d(d1),不更新信息

上表是上面那个图的大概的操作过程。每一个像素点,遇到深度较小的时候会更新,否则不更新。

深度缓冲是一个纹理,需要有特定的存储格式:

  1. DXGI_FORMAT_D32_FLOAT_S8X24_UINT: 指定32位浮点深度缓冲区,8位(无符号整数)预留给模板缓冲区映射到[0,255]范围,24位不用于填充。
  2. DXGI_FORMAT_D32_FLOAT: 指定一个32位浮点深度缓冲区。
  3. DXGI_FORMAT_D24_UNORM_S8_UINT: 指定一个映射到[0,1]范围的无符号24位深度缓冲区,并为模板缓冲区保留8位(无符号整数)映射到[0,255]范围。该缓冲区可以称为模板缓冲区,会在第11章详述。
  4. DXGI_FORMAT_D16_UNORM:指定映射到[0,1]范围的无符号16位深度缓冲区。

4.1.6 Resources and Descriptors

资源和描述符

渲染过程中,GPU会不断的写入和读取资源,在draw命令发出之前还需要绑定或者链接资源到即将要渲染的drawcall的渲染管道。有些资源每次渲染会变化,就需要更新绑定信息,但是GPU资源不是直接绑定。资源是通过描述符对象引用的,可以将其视为向GPU描述资源的轻量级结构,本质上,它是一种间接的层次;给定一个资源描述符,GPU可以获取实际的资源数据,并知道它的必要信息。

使用描述符的目的是让资源在GPU通用内存块上保持通用,这样在渲染管道的不同阶段都可以使用。

4.1.7 多重采样

抗锯齿/多重采样MSAA

显示器像素不是无穷小的,所有会有下面的这种情况:

上面是走样,下面是抗锯齿,通过采样和相邻像素来生成一个像素的最终颜色,使图像更平滑。

显示器分辨率增加,减小像素点的大小也可以显著减少走样现象。但是好的显示器比较贵。所以可以应用超采样技术(SSAA)。原理是让后缓冲区和深度缓冲区为屏幕分辨率的4倍大小,交换前后缓冲时,进行向下采样,每4个像素点的颜色平均成一个。超采样通过软件分辨率来实现。

但是超采样对内存和像素处理又多了较高的要求,满足条件的硬件也不是很便宜的。D3D支持一种折中的反锯齿拘束,称为多重采样。这种技术不用对每一个像素进行计算,它计算一次中心像素的颜色,然后基于可视性和覆盖性,将得到的信息分享给其他子像素(使一次计算,受惠4个像素,从而减少开销)。

4.1.8 DirectX中的MSAA

DXGI_SAMPLE_DESC

该结构有两个成员,定义如下:

1
2
3
4
typedef struct DXGI_SAMPLE_DESC {
	UINT Count; 
	UINT Quality;
} DXGI_SAMPLE_DESC;

Count成员指定每像素采样的数量,而Quality成员用于指定所需的质量级别(“质量级别”的含义可能因硬件制造商而异)。质量等级的范围取决于纹理格式和每像素采样的数量。

可以使用ID3D12Device::CheckFeatureSupport方法查询给定纹理格式的质量级别数量和样本数量。

4.1.9Feature Levels

特征级别

Direct3D 11引入了特征级别的概念(在代码中由D3D_FEATURE_LEVEL枚举类型表示),它大致对应于从版本9到版本11的各种Direct3D版本:

1
2
3
4
5
6
7
8
9
enum D3D_FEATURE_LEVEL {
	D3D_FEATURE_LEVEL_9_1 = 0x9100, 
	D3D_FEATURE_LEVEL_9_2 = 0x9200, 
	D3D_FEATURE_LEVEL_9_3 = 0x9300, 
	D3D_FEATURE_LEVEL_10_0 = 0xa000, 
	D3D_FEATURE_LEVEL_10_1 = 0xa100, 
	D3D_FEATURE_LEVEL_11_0 = 0xb000, 
	D3D_FEATURE_LEVEL_11_1 = 0xb100
}D3D_FEATURE_LEVEL;

特征级别定义了一组严格的功能。例如,一个支持feature level 11的GPU必须支持整个Direct3D 11功能集。如果用户的硬件不支持某个特征级别,则应用程序可以退回到旧的特征级别。

4.1.10 DirectX 图形基础设施

DirectX Graphics Infrastructure (DXGI) 是一个与Direct3D一起使用的API。基本思想是,一些与图形相关的任务对于多个图形api是通用的。例如,2D渲染API和3D渲染API一样需要交换链和页面翻转来获得平滑的动画;因此,交换链接口IDXGISwapChain(4.1.4)实际上是DXGI API的一部分。DXGI处理其他常见的图形功能,如全屏模式转换、枚举图形系统信息(如显示适配器、监视器和支持的显示模式(分辨率、刷新率等);它还定义了各种受支持的表面格式(DXGI_FORMAT)。

简要描述了一些DXGI概念和接口,将在Direct3D初始化过程中使用。DXGI的一个关键接口是IDXGIFactory接口,它主要用于创建IDXGISwapChain接口和枚举显示适配器。显示适配器实现图形化功能。通常,显示适配器是一个物理硬件(例如,显卡);但是,系统也可以有一个软件显示适配器来模拟硬件图形功能。一个系统可以有多个适配器(例如,如果它有多个显卡)。适配器由IDXGIAdapter接口表示。可以用以下代码枚举系统上的所有适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void D3DApp::LogAdapters() 
{
	UINT i = 0; IDXGIAdapter* adapter = nullptr; 
	std::vector<IDXGIAdapter*> adapterList; 
	while (mdxgiFactory->EnumAdapters(i, &adapter) !=DXGI_ERROR_NOT_FOUND) 
	{
		DXGI_ADAPTER_DESC desc; adapter->GetDesc(&desc);
		std::wstring text = L"***Adapter: "; 
		text += desc.Description; text += L"\n";
		OutputDebugString(text.c_str()); 
		adapterList.push_back(adapter);
		++i;
	}
	for (size_t i = 0; i < adapterList.size(); ++i)
	{ 
		LogAdapterOutputs(adapterList[i]); 
		ReleaseCom(adapterList[i]); 
	}
}

下面是这个方法的输出示例:

***Adapter: NVIDIA GeForce GTX 760

***Adapter: Microsoft Basic Render Driver

Microsoft Basic Render Driver在Win8及以上版本中都有。

一个系统可以有多个显示器。显示器是显示输出的一个例子。输出由IDXGIOutput接口表示。每个适配器都与输出列表相关联。例如,考虑一个具有两个显卡和三个显示器的系统,其中两个显示器连接到一个显卡,而第三个显示器连接到另一个显卡。在本例中,一个适配器有两个与之关联的输出,而另一个适配器有一个与之关联的输出。我们可以用以下代码枚举与适配器关联的所有输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter) 
{
	UINT i = 0; IDXGIOutput* output = nullptr; 
	while (adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND) 
	{
		DXGI_OUTPUT_DESC desc; output->GetDesc(&desc);
		std::wstring text = L"***Output: "; 
		text += desc.DeviceName; text += L"\n"; 
		OutputDebugString(text.c_str());
		LogOutputDisplayModes(output, DXGI_FORMAT_B8G8R8A8_UNORM);
		ReleaseCom(output);
		++i;
	}
}

注意,根据文档,“Microsoft Basic Render Driver”没有显示输出。每个监视器都支持一组显示模式。显示模式是指DXGI_MODE_DESC中的以下数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct DXGI_MODE_DESC {
	UINT Width; // Resolution width UINT Height; 
				// Resolution height DXGI_RATIONAL RefreshRate; DXGI_FORMAT Format; 
				// Display format DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; 
				// Progressive
	vs.interlaced DXGI_MODE_SCALING Scaling; 
	// 图像延伸到显示器上的方法。
} DXGI_MODE_DESC;
typedef struct DXGI_RATIONAL {
	UINT Numerator; 
	UINT Denominator;
} DXGI_RATIONAL;
typedef enum DXGI_MODE_SCANLINE_ORDER {
	DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED = 0, 
	DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE = 1, 
	DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST = 2, 
	DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST = 3
} DXGI_MODE_SCANLINE_ORDER;
typedef enum DXGI_MODE_SCALING {
	DXGI_MODE_SCALING_UNSPECIFIED = 0, 
	DXGI_MODE_SCALING_CENTERED = 1,
	DXGI_MODE_SCALING_STRETCHED = 2
} DXGI_MODE_SCALING;

固定显示模式格式,可以得到一个列表的所有支持的显示模式的输出支持的格式与以下代码:

下面是这段代码的一些输出示例:

***Output: \\.\DISPLAY2

 …

Width = 1920 Height = 1080 Refresh = 59950/1000

Width = 1920 Height = 1200 Refresh = 59950/1000

在进入全屏模式时,枚举显示模式尤为重要。为了获得最佳的全屏性能,指定的显示模式(包括刷新率)必须与监视器支持的显示模式完全匹配。指定枚举显示模式保证了这一点。

更多资料:

 DXGI Overview:

https://docs.microsoft.com/zh-cn/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi?redirectedfrom=MSDN

DirectX Graphics Infrastructure:

Best Improvements:

http://msdn.microsoft.com/en-us/library/windows/desktop/ee417025(v=vs.85).aspx

DXGI 1.4 Improvements:

https://msdn.microsoft.com/en-us/library/windows/desktop/mt427784%28v=vs.85%29.aspx

4.1.11检查特性支持

使用ID3D12Device::CheckFeatureSupport方法来检查当前图形驱动程序的多采样支持。这只是可以用这个函数检查的一个特性支持。该方法的原型如下:

HRESULT ID3D12Device::CheckFeatureSupport(

D3D12_FEATURE Feature,

 void *pFeatureSupportData,

UINT FeatureSupportDataSize);

译文:

1. Feature: D3D12_FEATURE枚举类型的一个成员,标识我们想要检查的特性的类型:

  1. D3D12_FEATURE_D3D12_OPTIONS:检查对各种Direct3D 12特性的支持。
  2. D3D12_FEATURE_ARCHITECTURE:检查对硬件架构特性的支持。
  3. D3D12_FEATURE_FEATURE_LEVELS:检查支持特性级别
  4. 4D3D12_FEATURE_FORMAT_SUPPORT:检查给定纹理格式的特性支持(例如,该格式是否可以用作渲染目标,该格式是否可以与混合一起使用)。
  5. D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS:检查多采样特性是否支持。

2.pFeatureSupportData:指向数据结构的指针,用于检索特性支持信息。使用的结构类型取决于指定的特征参数:

  • D3D12_FEATURE_D3D12_OPTIONS—>D3D12_FEATURE_DATA_D3D12_OPTIONS.
  • D3D12_FEATURE_ARCHITECTURE—>D3D12_FEATURE_DATA_ARCHITECTURE
  • D3D12_FEATURE_FEATURE_LEVELS—>D3D12_FEATURE_DATA_FEATURE_LEVELS.
  • D3D12_FEATURE_FORMAT_SUPPORT—>D3D12_FEATURE_DATA_FORMAT_SUPPORT.
  • D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS—>D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS

.FeatureSupportDataSize:参数传递到pFeatureSupportData的数据结构的大小。

ID3D12Device::CheckFeatureSupport函数检查对许多特性的支持,并且是高级的特性;有关每个特性结构的数据成员的详细信息,请参阅SDK文档。下面有一个例子展示了如何检查支持的特性级别(§4.1.9):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct D3D12_FEATURE_DATA_FEATURE_LEVELS 
{ UINT NumFeatureLevels; 
const D3D_FEATURE_LEVEL* pFeatureLevelsRequested; 
D3D_FEATURE_LEVEL MaxSupportedFeatureLevel; 
} D3D12_FEATURE_DATA_FEATURE_LEVELS;
 
D3D_FEATURE_LEVEL featureLevels[3] = 
{
D3D_FEATURE_LEVEL_11_0, // First check D3D 11 support 首先检查是否支持11
D3D_FEATURE_LEVEL_10_0, // Next, check D3D 10 support 再检查是否支持10
D3D_FEATURE_LEVEL_9_3   // Finally, check D3D 9.3 support 最后检查9.3
};
D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevelsInfo; 
featureLevelsInfo.NumFeatureLevels = 3; 
featureLevelsInfo.pFeatureLevelsRequested = featureLevels; 
md3dDevice->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featureLevelsInfo,sizeof(featureLevelsInfo));

4.1.12 Residency

驻留期

一个复杂的游戏会使用大量的资源,比如纹理和3D网格,但是GPU并不总是需要这些资源。

在Direct3D 12中,应用程序管理资源驻留(一个资源是否在GPU内存中)的方法是从GPU内存中清除资源,然后根据需要使它们再次驻留在GPU上。基本的想法是最小化应用程序使用的GPU内存,因为可能没有足够的内存来存储整个游戏的所有资源,或者用户运行的其他应用程序需要GPU内存。作为一个性能说明,应用程序应该避免在很短的时间内交换相同的GPU内存中的资源,因为这样做有开销。游戏关卡或者区域的改变是改变资源驻留时间的应用场景。

默认情况下,当一个资源被创建时,它是常驻的,当资源被销毁时就会GPU中清除。当然有手动控制资源常驻的方法:

HRESULT ID3D12Device::MakeResident(UINT NumObjects, ID3D12Pageable* const* ppObjects);

HRESULT ID3D12Device::Evict(UINT NumObjects, ID3D12Pageable* const* ppObjects);

对于这两种方法,第二个参数是ID3D12Pageable资源的数组,第一个参数是数组中的资源数量。

更多参考:

https://msdn.microsoft.com/en-us/library/windows/desktop/mt186622%28v=vs.85%29.aspx