最近正在学习Directx 11的开发,上一次记录了Win32应用程序的基础,主要是注册窗体类,加载图标和鼠标,处理事件循环等,链接为:C++Directx11开发笔记一:Win32应用程序窗体创建。今天我们要介绍的是Direct3D基础,其中包括Direct3D 11的设备初始化等等来初步认识Direct3D这个神秘的面纱。由于要用到Directx11所以需要在项目中引入一些库,为了便于后面操作,这里列举了所需要的常见的库名称:d3d11.lib(d3d11d.lib调试下),d3dcompiler.lib(HLSL编译器,HLSL开发一:语言基础),dxerr.lib(错误库),dxguid.lib(这个库定义了Direct3D中所需要的GUID,即COM需要引用的,由于Directx是一个COM组建哦,所以如果不引入会出现问题的哦)。以上这些就是常见的Direct3D在C++编程中需要引入的库,可以在解决方案中的链接输入中引入,这个是C++基础这里就不多说了哦。
设备(Device):即是用于创建资源和计算显示接口性能的东东,大家知道这个东东和显卡有关系,不一样的显卡支持的也有所不同,在Direct3D中使用ID3D11Device【注:在Direct3D 11中的命名都是以D3D11开头,其中I表示接口的意思】接口来表示。每个应用程序必须有最少一个设备实例,即依赖于你机子上安装的显卡设备。ID3D11Device由D3D11CreateDevice或D3D11CreateDeviceAndSwapChain来创建,当然指定那个驱动程序,Direct3D是直接和驱动打交道的,如果硬件不支持,Direct3D将会虚拟(即通过软件)而得到支持。就需要D3D_DRIVER_TYPE来指定,具体代码可以在D3Dcommon.h中找到,代码如下:
1
typedef
2
enum
D3D_DRIVER_TYPE
3
{ D3D_DRIVER_TYPE_UNKNOWN
=
0
,
4
D3D_DRIVER_TYPE_HARDWARE
=
( D3D_DRIVER_TYPE_UNKNOWN
+
1
) ,
5
D3D_DRIVER_TYPE_REFERENCE
=
( D3D_DRIVER_TYPE_HARDWARE
+
1
) ,
6
D3D_DRIVER_TYPE_NULL
=
( D3D_DRIVER_TYPE_REFERENCE
+
1
) ,
7
D3D_DRIVER_TYPE_SOFTWARE
=
( D3D_DRIVER_TYPE_NULL
+
1
) ,
8
D3D_DRIVER_TYPE_WARP
=
( D3D_DRIVER_TYPE_SOFTWARE
+
1
)
9
} D3D_DRIVER_TYPE;
其中:
- D3D_DRIVER_TYPE_UNKNOWN:未知驱动类型;
- D3D_DRIVER_TYPE_HARDWARE:硬件驱动类型,这个是性能最好的,因为得到硬件显卡的支持,可以使用硬件加速器,可以在主程序中使用这个类型,但是也有些管道不支持硬件需要软件的支持(即显卡不可能支持Directx11中所有的东东,不过可以使用软件驱动类型来帮忙)。这种类型的驱动主要应用于HAL(Hardware Abstraction Layer),美其名曰硬件抽象层;
- D3D_DRIVER_TYPE_REFERENCE:引用驱动类型,这个支持所有Direct3D接口,因为这个驱动通过DirectX SDK进行安装。这个设计主要是为了进行测试,检验性能,调试以及验证BUG等而存活的;
- D3D_DRIVER_TYPE_NULL:空驱动类型,这个主要是用来调试没有呈现的APIs调用,即不需要显示,这个驱动也是有DirectX SDK进行安装;
- D3D_DRIVER_TYPE_SOFTWARE:软件驱动类型,这个类型的驱动完全有软件模拟来实现Direct3D,性能比较差,但是有时候显卡不支持的情况下需要这个来完成进行模拟;
- D3D_DRIVER_TYPE_WARP:WARP驱动类型,是一个性能强的软件光栅化器,这个光栅化器支持9_1到10.1版本,具有比较强的性能,【注:这个比较难理解,以后可能会专门研究一下!】
在上面提到的9_x等应该就是DirectX的版本,我们学习的当然是11_0,这个在编程中也有一个枚举,具体代码如下,估计以后的12,13出来也会加到SDK中。
typedef
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;
DirectX SDK中支持DirectX 9.x和DirectX 10.x以及我们学习的DirectX 11,根据市面上的显卡基本可以支持DirectX10了,所以我们可以在代码中定义一下可能用到的驱动类型和SDK支持版本等级(这个定义是为了可以迭代进行解决一些麻烦,这个和网页AJAX编程中创建REQUEST一样,因为各个浏览器支持的XML东东是不一样的),代码如下:
1
D3D_DRIVER_TYPE driverTypes[]
=
2
{
3
D3D_DRIVER_TYPE_HARDWARE,
4
D3D_DRIVER_TYPE_WARP,
5
D3D_DRIVER_TYPE_REFERENCE,
6
};
7
UINT numDriverTypes
=
ARRAYSIZE( driverTypes );
8
9
D3D_FEATURE_LEVEL featureLevels[]
=
10
{
11
D3D_FEATURE_LEVEL_11_0,
12
D3D_FEATURE_LEVEL_10_1,
13
D3D_FEATURE_LEVEL_10_0,
14
};
15
UINT numFeatureLevels
=
ARRAYSIZE( featureLevels );
设备上下文(Device Context):主要包括当前设备的环境以及一些设定信息【注:如果你是ASP.NET的开发人员,一定知道网页HTTP请求的上下文HttpContext吧】。设备上下文主要用于设置管道状态和通过所拥有的资源运行呈现命令。在Direct3D中包含了两种设备上下文,一种为立即呈现(显示)[Immediate Context],还有一种就是延迟显示[Deferred Context]。两种上下文都通过ID3D11DeviceContext接口来描述,下面我们分别看一下这两种的设备上下文信息。
- ImmediateContext:立即呈现器直接和驱动打交道,每一个设备有且仅有一个立即显示设备上下文,他可以直接从GPU中检索得到数据。
- DeferredContext:延迟显示主要用于多线程应用程序中,将GPU的命令添加到一个队列中,在单线程应用程序中是没有必要这么做的,直接显示就好了。使用延迟显示将不会继承立即呈现的状态信息,而是通过一个工作线程(worker thread)来呈现执行命令。
注意:在DirectX11中所有ID3D11Device以及ID3D11DeviceChild(包括它衍生出来的ID3D11Buffer,ID3D11Query等等)都是线程安全的。即多个线程可以同时调用它们,而不会出现冲突状态。但是设备上下文不一样,无论是那个设备上下文都可以工作在任何线程中,但是每一次只能有一个线程进行工作,因此如果需要在多线程中使用就需要记得“锁”和“解锁”。
初始化设备:
在创建设备以及设备上下文时我们还需要了解一个DXGI_SWAP_CHAIN_DESC结构体,应该叫做DXGI交换链描述信息,其具体结构代码如下:
1
typedef
struct
DXGI_SWAP_CHAIN_DESC
2
{
3
DXGI_MODE_DESC BufferDesc;
4
DXGI_SAMPLE_DESC SampleDesc;
5
DXGI_USAGE BufferUsage;
6
UINT BufferCount;
7
HWND OutputWindow;
8
BOOL Windowed;
9
DXGI_SWAP_EFFECT SwapEffect;
10
UINT Flags;
11
} DXGI_SWAP_CHAIN_DESC;
12
typedef
struct
DXGI_MODE_DESC
13
{
14
UINT Width;
15
UINT Height;
16
DXGI_RATIONAL RefreshRate;
17
DXGI_FORMAT Format;
18
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
19
DXGI_MODE_SCALING Scaling;
20
} DXGI_MODE_DESC;
21
22
typedef
struct
DXGI_SAMPLE_DESC
23
{
24
UINT Count;
25
UINT Quality;
26
} DXGI_SAMPLE_DESC;
27
typedef
28
enum
DXGI_SWAP_EFFECT
29
{ DXGI_SWAP_EFFECT_DISCARD
=
0
,
30
DXGI_SWAP_EFFECT_SEQUENTIAL
=
1
31
} DXGI_SWAP_EFFECT;
这个结构体的相关信息将在后面进行描述,因为这个属于DXGI范畴,我想也应该相应了解它们的具体情况(在我暂时学习的情况了解到学习DirectX11肯定要解决HLSL,DXGI等)。这里我们只要知道如何初始化即可,其代码如下:
1
DXGI_SWAP_CHAIN_DESC sd;
2
ZeroMemory(
&
sd,
sizeof
( sd ) );
3
sd.BufferCount
=
1
;
4
sd.BufferDesc.Width
=
width;
5
sd.BufferDesc.Height
=
height;
6
sd.BufferDesc.Format
=
DXGI_FORMAT_R8G8B8A8_UNORM;
7
sd.BufferDesc.RefreshRate.Numerator
=
60
;
8
sd.BufferDesc.RefreshRate.Denominator
=
1
;
9
sd.BufferUsage
=
DXGI_USAGE_RENDER_TARGET_OUTPUT;
10
sd.OutputWindow
=
g_hWnd;
11
sd.SampleDesc.Count
=
1
;
12
sd.SampleDesc.Quality
=
0
;
13
sd.Windowed
=
TRUE;
其中width和height表示窗口(游戏窗口)的宽和高,而g_hWnd表示窗体的句柄,60应该就是表示帧频率(FPS),即每秒显示60帧。这样就可以通过D3D11CreateDevice或D3D11CreateDeviceAndSwapChain来得到设备和设备上下文了,具体代码如下:
1
HINSTANCE g_hInst
=
NULL;
2
HWND g_hWnd
=
NULL;
3
D3D_DRIVER_TYPE g_driverType
=
D3D_DRIVER_TYPE_NULL;
4
D3D_FEATURE_LEVEL g_featureLevel
=
D3D_FEATURE_LEVEL_11_0;
5
ID3D11Device
*
g_pd3dDevice
=
NULL;
6
ID3D11DeviceContext
*
g_pImmediateContext
=
NULL;
7
IDXGISwapChain
*
g_pSwapChain
=
NULL;
8
ID3D11RenderTargetView
*
g_pRenderTargetView
=
NULL;
9
//
上面为我们定义的一些全局变量,下面代码在初始化设备中进行调用
10
for
( UINT driverTypeIndex
=
0
; driverTypeIndex
<
numDriverTypes; driverTypeIndex
++
)
11
{
12
g_driverType
=
driverTypes[driverTypeIndex];
13
hr
=
D3D11CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
14
D3D11_SDK_VERSION,
&
sd,
&
g_pSwapChain,
&
g_pd3dDevice,
&
g_featureLevel,
&
g_pImmediateContext );
15
if
( SUCCEEDED( hr ) )
16
break
;
17
}
18
if
( FAILED( hr ) )
19
return
hr;
创建目标程序区域(访问端口ViewPort):
初始化设备后就需要创建我们需要进行呈现区域,包括它的大小等等,首先我们先看一下代码,然后我们再进行分析,这样有助于我们对他的了解,具体代码如下:
1
//
Create a render target view
2
ID3D11Texture2D
*
pBackBuffer
=
NULL;
3
hr
=
g_pSwapChain
->
GetBuffer(
0
, __uuidof( ID3D11Texture2D ), ( LPVOID
*
)
&
pBackBuffer );
4
if
( FAILED( hr ) )
5
return
hr;
6
7
hr
=
g_pd3dDevice
->
CreateRenderTargetView( pBackBuffer, NULL,
&
g_pRenderTargetView );
8
pBackBuffer
->
Release();
9
if
( FAILED( hr ) )
10
return
hr;
11
12
g_pImmediateContext
->
OMSetRenderTargets(
1
,
&
g_pRenderTargetView, NULL );
13
14
//
Setup the viewport
15
D3D11_VIEWPORT vp;
16
vp.Width
=
(FLOAT)width;
17
vp.Height
=
(FLOAT)height;
18
vp.MinDepth
=
0.0f
;
19
vp.MaxDepth
=
1.0f
;
20
vp.TopLeftX
=
0
;
21
vp.TopLeftY
=
0
;
22
g_pImmediateContext
->
RSSetViewports(
1
,
&
vp );
由于我们是在单线程中,所以使用立即显示的上下文,首先通过交换链的GetBuffer方法来分配一个2D纹理缓存区,从而创建并设置立即呈现上下文访问目标区域,最后设置访问区域大小。这样设备初始化到此就基本结束了,接下来我们在消息循环中就可以呈现了。如下代码:
1
void
Render()
2
{
3
//
Just clear the backbuffer
4
float
ClearColor[
4
]
=
{
0.0f
,
0.125f
,
0.3f
,
1.0f
};
//
red,green,blue,alpha
5
g_pImmediateContext
->
ClearRenderTargetView( g_pRenderTargetView, ClearColor );
6
g_pSwapChain
->
Present(
0
,
0
);
7
}
通过交换链来呈现图形,更复杂的程序等等将在后面跟着例子慢慢学习,希望这次我也可以系统的学习一下C++DirectX11编程。由于我学习C++是由.NET转过来的,以前也做了很多.NET项目,最近也有在弄MVC所以当中可能会涉及到一些.NET的影子。在最后要注意的是DirectX是COM,在使用后要记得Release就OK了。由于本人也是半路出家,程序都是自学的,所以难免有些错误,再加上英文又不好,希望各位博友能够批评指正,大家共同进步。