Direct3D初始化过程分为以下几个步骤:
-
创建设备和设备上下文;
-
检测设备支持的多重采样质量等级;
-
填充用于描述交换链的结构体;
-
创建交换链;
-
为交换链的后台缓冲区创建渲染目标视图;
-
创建深度/模板缓冲区以及相关的深度/模板视图;
-
将渲染目标视图和深度/模板视图绑定到渲染管线的输出合并阶段;
-
设置视口。
一、创建设备和设备上下文
ID3D11Device接口用于检测显示适配器功能和分配资源;
ID3D11DeviceContext接口用于设置管线状态、将资源绑定到图形管线和生成渲染命令;
设备和上下文用如下函数创建:
HRESULT D3D11CreateDevice (
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software ,
UINT Flags ,
CONST D3D_FEATURE_LEVEL *pFeatureLevels ,
UINT FeatureLevels ,
UINT SDKVersion,
ID3D11Device **ppDevice ,
D3D_FEATURE_LEVE L *pFeatureLevel,
ID3D11DeviceContext **ppImmediateContext
);
参数信息如下:
pAdapter | 指定要为哪个物理显卡创建设备对象。当该参数设为空值时,表示使用主显卡。 |
DriverType | D3D_DRIVER_TYPE_HARDWARE:表示使用3D硬件来加快渲染速度。但是,也可以有两个其他选择: D3D_DRIVER_TYPE_REFERENCE:创建所谓的引用设备(reference device)。引用设备是Direct3D的软件实现,它具有Direct3D的所有功能(只是运行速度非常慢,因为所有的功能都是用软件来实现的)。引用设备随DirectX SDK一起安装,只用于程序员,而不应该用于程序发布。使用引用设备有两个原因:
D3D_DRIVER_TYPE_SOFTWARE:创建一个用于模拟3D硬件的软件驱动器。要使用软件驱动器,你必须自己创建一个,或使用第三方的软件驱动器。与下面要说的WARP驱动器不同,Direct3D不提供软件驱动器。 D3D_DRIVER_TYPE_WARP:创建一个高性能的Direct3D 10.1软件驱动器。WARP代表Windows Advanced Rasterizati on Platform。 |
Software | 用于支持软件光栅化设备(software rasterizer)。总是将该参数设为空值 |
Flags | 可选的设备创建标志值。当以release模式生成程序时,该参数通常设为0(无附加标志值);当以debug模式生成程序时,该参数应设为:D3D11_CREATE_DEVICE_DEBUG:用以激活调试层。 |
pFeatureLevels | D3D_FEATURE_LEVEL数组,元素的顺序表示要特征等级的测试顺序 |
FeatureLevels | pFeatureLevels数组中的元素D3D_FEATURE_LEVELs的数量,若pFeatureLevels设置为null,则这个值为0 |
SDKVersion | 始终设为D3D11_SDK_VERSION |
ppDevice | 返回创建后的设备对象 |
pFeatureLevel | 返回pFeatureLevels数组中第一个支持的特征等级(如果pFeatureLevels 为null,则返回可支持的最高等级) |
ppImmediateContext | 返回创建后的设备上下文 |
调用该函数的代码如下:
UINT createDeviceFlags = 0;
#if defined(DEBUG)||defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
ID3D11Device * md3dDevice;
ID3D11Device Context* md3dImmediate Context;
HRESULT hr = D3D11CreateDevice(
0, // 默认显示适配器
D3D_DRIVER_TYPE_HARDWARE ,
0, // 不使用软件设备
createDeviceFlags ,
0, 0, // 默认的特征等级数组
D3D11_SDK_VERSION,
& md3dDevice ,
& featureLevel,
& md3dImmediateContext);
if(FAILED(hr) )
{
MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
return false ;
}
if(featureLevel != D3D_FEATURE_LEVEL_11_0)
{
MessageBox(0, L"Direct3D FeatureLevel 11 unsupported.", 0, 0);
return false;
}
二、检测多重采样质量支持
UINT m4xMsaaQuality;
HR(md3dDevice ->CheckMultisampleQualityLevels(
DXGI_FORMAT_R8G8B8A8_UNORM, 4, & m4xMsaaQuality));
assert(m4xMsaaQuality > 0);
三、描述交换链
此步骤需要填充一个DXGI_SWAP_CHAIN_DESC结构体来描述我们将要创建的交换链的特性。该结构体的定义如下:
typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;
DXGI_MODE_DESC类型是另一个结构体,其定义如下:
typedef struct DXGI_MODE_DESC
{
UINT Width; // 后台缓冲区宽度
UINT Height; // 后台缓冲区高度
DXGI_RATIONAL RefreshRate; // 显示刷新率
DXGI_FORMAT Format; // 后台缓冲区像素格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 显示扫描线模式
DXGI_MODE_SCALING Scaling; // 显示扫描线模式
} DXGI_MODE_DESC;
参数信息如下:
BufferDesc | 该结构体描述了我们所要创建的后台缓冲区的属性。主要关注的属性有:宽度、高度和像素格式 |
SampleDesc | 多重采样数量和质量级别 |
BufferUsage | 设为DXGI_USAGE_RENDER_TARGET_OUTPUT,因为要将场景渲染到后台缓冲区(即,将它用作渲染目标) |
BufferCount | 交换链中的后台缓冲区数量;一般只用一个后台缓冲区来实现双缓存 |
OutputWindow | 将要渲染到的窗口的句柄 |
Windowed | 当设为true时,程序以窗口模式运行;当设为false时,程序以全屏(full-screen)模式运行 |
SwapEffect | 设为DXGI_SWAP_EFFECT_DISCARD,让显卡驱动程序选择最高效的显示模式 |
Flags | 可选的标志值 |
调用该函数的代码如下:
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth; // 使用窗口客户区宽度
sd.BufferDesc.Height = mClientHeight;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
// 是否使用4X MSAA?
if(mEnable4xMsaa)
{
sd.SampleDesc.Count = 4;
// m4xMsaaQuality是通过CheckMultisampleQualityLevels()方法获得的
sd.SampleDesc.Quality = m4xMsaaQuality-1;
}
// NoMSAA
else
{
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
}
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;
四、创建交换链
交换链(IDXGISwapChain)是通过IDXGIFactory实例的IDXGIFactory::CreateSwapChain方法创建的:
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice , // 指向ID3D11Device的指针
DXGI_SWAP_CHAIN_DESC *pDesc, // 指向一个交换链描述的指针
IDXGISwapChain **ppSwapChain); // 返回创建后的交换链
五、创建渲染目标视图
创建渲染目标视图代码实现过程如下:
ID3D11RenderTargetView* mRenderTargetView;
ID3D11Texture2D* backBuffer;
mSwapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&backBuffer));
md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView);
ReleaseCOM(backBuffer);
1.IDXGISwapChain::GetBuffer方法用于获取一个交换链的后台缓冲区指针。该方法的第一个参数表示所要获取的后台缓冲区的索引值(由于后台缓冲区的数量可以大于1,所以这里必须指定索引值)。在示例程序中,只使用一个后台缓冲区,所以该索引值设为0。第二个参数是缓冲区的接口类型,它通常是一个2D纹理(ID3D11Texture2D)。第三个参数返回指向后台缓冲区的指针。
2.使用ID3D11Device::CreateRenderTargetView方法创建渲染目标视图。
第一个参数指定了将要作为渲染目标的资源,在上面的例子中,渲染目标是后台缓冲区(即为后台缓冲区创建了一个渲染目标视图)。
第二个参数是一个指向D3D11_RENDER_TARGET_VIEW_DESC结构体的指针,该结构体描述了资源中的元素的数据类型。如果在创建资源时使用的是某种强类型格式(即,非弱类型格式),则该参数可以为空,表示以资源的第一个mipmap层次(后台缓冲区也只有一个mipmap层次)作为视图格式。第三个参数通过指针返回了创建后的渲染目标视图对象。
3.每调用一次IDXGISwapChain::GetBuffer方法,后台缓冲区的COM引用计数就会向上递增一次,这便是在代码片段的结尾处释放它(ReleaseCOM)的原因。
六、创建深度模板缓冲区和视图
深度缓冲区只是一个存储深度信息的2D纹理(如果使用模板,则模板信息也在该缓冲区中)。要创建纹理首先填充D3D11_TEXTURE2D_DESC结构体来描述所要创建的纹理,然后再调用ID3D11Device::CreateTexture2D方法。该结构体的定义如下:
typedef struct D3D11_TEXTURE2D_DESC {
UINT Width;
UINT Height;
UINT MipLevels;
UINT ArraySize;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D10_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
} D3D11_TEXTURE2D_DESC;
参数信息如下:
Width | 纹理的宽度,单位为纹理元素(texel) |
Height | 纹理的高度,单位为纹理元素(texel) |
MipLevels | 多级渐近纹理层(mipmap level)的数量 |
ArraySize | 在纹理数组中的纹理数量。对于深度/模板缓冲区来说,只需要一个纹理 |
Format | 一个DXGI_FORMAT枚举类型成员,它指定了纹理元素的格式。对于深度/模板缓冲区来说,它必须DXGI_FORMAT枚举列出的格式之一 |
SampleDesc | 多重采样数量和质量级别 |
Usage | 表示纹理用途的D3D11_USAGE枚举类型成员。有4个可选值: D3D11_USAGE_DEFAULT:表示GPU(graphics processing unit,图形处理器)会对资源执行读写操作。CPU不能读写这种资源。对于深度/模板缓冲区,使用D3D11_USAGE_DEFAULT标志值,因为GPU会执行所有读写深度/模板缓冲区的操作; D3D10_USAGE_IMMUTABLE:表示在创建资源后,资源中的内容不会改变。这样可以获得一些内部优化,因为GPU会以只读方式访问这种资源。除了在创建资源时CPU会写入初始化数据外,其他任何时候CPU都不会对这种资源执行任何读写操作; D3D10_USAGE_DYNAMIC:表示应用程序(CPU)会频繁更新资源中的数据内容(例如,每帧更新一次)。GPU可以从这种资源中读取数据,而CPU可以向这种资源中写入数据; D3D10_USAGE_STAGING:表示应用程序(CPU)会读取该资源的一个副本(即,该资源支持从显存到系统内存的数据复制操作); |
BindFlags | 指定该资源将会绑定到管线的哪个阶段。对于深度/模板缓冲区,该参数应设为D3D11_BIND_DEPTH_STENCIL。其他可用于纹理的绑定标志值还有: D3D11_BIND_RENDER_TARGET:将纹理作为一个渲染目标绑定到管线上; D3D11_BIND_SHADER_RESOURCE:将纹理作为一个着色器资源绑定到管线上; |
CPUAccessFlags | 指定CPU对资源的访问权限。如果CPU需要向资源写入数据,则应指定D3D11_CPU_ACCESS_WRITE。具有写访问权限的资源的Usage参数应设为D3D11_USAGE_DYNAMIC或D3D11_USAGE_STAGING。如果CPU需要从资源读取数据,则应指定D3D11_CPU_ACCESS_READ。具有读访问权限的资源的Usage参数应设为D3D11_USAGE_STAGING。对于深度/模板缓冲区来说,只有GPU会执行读写操作;所以,将该参数设为0,因为CPU不会在深度/模板缓冲区上执行读写操作 |
MiscFlags | 可选的标志值,与深度/模板缓冲区无关,所以设为0 |
在使用深度/模板缓冲区之前,必须为它创建一个绑定到管线上的深度/模板视图。过程与创建渲染目标视图的过程相似。下面的代码示范了如何创建深度/模板纹理以及与它对应的深度/模板视图:
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// 是否使用4X MSAA?——必须与交换链的MSAA的值匹配
if( mEnable4xMsaa)
{
depthStencilDesc.SampleDesc.Count = 4;
depthStencilDesc.SampleDesc.Quality = m4xMsaaQuality-1;
}
// 不使用MSAA
else
{
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
}
depthStencilDesc.Usage = D3D10_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ID3D10Texture2D* mDepthStencilBuffer;
ID3D10DepthStencilView* mDepthStencilView;
HR(md3dDevice->CreateTexture2D(
&depthStencilDesc, 0, &mDepthStencilBuffer));
HR(md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer, 0, &mDepthStencilView));
CreateTexture2D的第二个参数是一个指向初始化数据的指针,这些初始化数据用来填充纹理。不过,由于个纹理被用作深度/模板缓冲区,所以不需要为它填充任何初始化数据。当执行深度缓存和模板操作时,Direct3D会自动向深度/模板缓冲区写入数据。所以,将第二个参数指定为空值。
CreateDepthStencilView的第二个参数是一个指向D3D11_DEPTH_STENCIL_VIEW_DESC的指针。这个结构体描述了资源中这个元素数据类型(格式)。如果资源是一个有类型的格式(非typeless),这个参数可以为空值,表示创建一个资源的第一个mipmap等级的视图(深度/模板缓冲也只能使用一个 mipmap等级)。因为指定了深度/模板缓冲的格式,所以将这个参数设置为空值。
七、将视图绑定到输出合并阶段
为后台缓冲区和深度模板缓冲区创建好视图后,就可以将这些视图绑定到渲染管线的输出合并阶段,使这些资源成为管线的渲染目标和深度/模板缓冲区:
md3dImmediateContext->OMSetRenderTargets(
1,&mRenderTargetView,mDepthStencilView);
第一个参数是将要绑定的渲染目标的数量;在这里仅绑定了一个渲染目标,不过该参数可以为着色器同时绑定多个渲染目标。
第二个参数是将要绑定的渲染目标视图数组中的第一个元素的指针。第三个参数是将要绑定到管线的深度/模板视图。
八、设置视口
typedef struct D3D11_VIEWPORT {
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D11_VIEWPORT;
前4个数据成员定义了相对于窗口客户区的视口矩形范围。MinDepth成员表示深度缓冲区的最小值,MaxDepth表示深度缓冲区的最大值。在填充了D3D11_VIEWPORT结构体之后,使用ID3D11Device::RSSetViewports方法设置Direct3D的视口。下面的例子创建和设置了一个视口,该视口与整个后台缓冲区的大小相同:
D3D11_VIEWPORT vp;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
vp.Width = static_cast<float>(mClientWidth);
vp.Height = static_cast<float>(mClientHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
md3dImmediateContext-->RSSetViewports(1, &vp);
第一个参数是绑定的视图的数量(可以使用超过1的数量用于高级的效果),第二个参数指向一个viewports的数组。