Direct3D11学习:(三)Direct3D11初始化

转载请注明出处:http://www.cnblogs.com/Ray1024

一、概述

做完一系列的准备工作之后,我们就正式进入Direct3D11的学习了。我们就从Direct3D11的初始化工作开始我们的学习之路。

这篇文章主要介绍了在一个空的Win32程序中,从头开始D3D11的初始化过程。

二、D3D11的初始化步骤

2.1 创建设备(Device)和上下文(Context)

要初始化D3D11,首先需要创建D3D11设备(ID3D11Device)和上下文(ID3D11DeviceContext)。它们是是最重要的DD接口,可以被看成是物理图形设备硬件的软控制器;也就是说,我们可以通过该接口与硬件进行交互,命令硬件完成一些工作(比如:在显存中分配资源、清空后台缓冲区、将资源绑定到各种管线阶段、绘制几何体)。具体而言:

  a.ID3D11Device接口用于检测显示适配器功能和分配资源。

  b.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			指定要为哪个物理显卡创建设备对象。为NULL时,表示使用主显卡;
	DriverType			设置驱动类型,一般选择硬件加速D3D_DRIVER_TYPE_HARDWARE,此时下一个参数就是NULL;
	Flags				为可选参数,一般为NULL,可以设为D3D11_CREATE_DEVICE_DEBUG或D3D11_CREATE_DEVICE_SINGLETHREADED,
						或两者一起,前者让要用于调试时收集信息,后者在确定程序只在单线程下运行时设置为它,可以提高性能;
    pFeatureLevels		为我们提供给程序的特征等级的一个数组,下一个参数为数组中元素个数;
	SDKVersion			恒定为D3D11_SDK_VERSION;
	ppDevice			为设备指针的地址,注意设备是指针类型,这里传递的是指针的地址(二维指针,d3d程序中所有的接口都声明为指针类型!);
    pFeatureLevel		为最后程序选中的特征等级,我们定义相应的变量,传递它的地址进来;
    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;
}

上下文有两种:立即执行上下文(immediate context)和延迟执行上下文(ID3D11Device::CreateDeferredContext),这里我们使用立即执行上下文。延迟执行上下文主要用于D3D11支持的多线程编程。

2.2 检测4X多重采样质量支持

创建了设备后,我们就可以检查4X多重采样质量等级了。所有支持D3D11的设备都支持所有渲染目标格式的4X MSAA(支持的质量等级可能并不相同)。

UINT  m4xMsaaQuality;
HR(md3dDevice ->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &  m4xMsaaQuality));
assert(m4xMsaaQuality > 0);

因为4X MSAA总是被支持的,所以返回的质量等级总是大于0。

2.3 描述交换链

下一步是创建交换链,首先需要填充一个DXGI_SWAP_CHAIN_DESC结构体来描述我们将要创建的交换链的特性。该结构体的定义如下:

typedef struct DXGI_MODE_DESC
{
    UINT Width;									// 后台缓冲区宽度
    UINT Height;								// 后台缓冲区高度
    DXGI_RATIONAL RefreshRate;					// 显示刷新率
    DXGI_FORMAT Format;							// 后台缓冲区像素格式
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;	// display scanline mode
    DXGI_MODE_SCALING Scaling;					// display scaling mode
} DXGI_MODE_DESC;

typedef struct DXGI_SWAP_CHAIN_DESC {
    DXGI_MODE_DESC BufferDesc; 		// 上面介绍的结构体类型,描述了我们所要创建的后台缓冲区的属性
    DXGI_SAMPLE_DESC SampleDesc; 	// 多重采样数量和质量级别
    DXGI_USAGE BufferUsage; 		// 对于交换链,为DXGI_USAGE_RENDER_TARGET_OUTPUT
    UINT BufferCount; 				// 交换链中的后台缓冲区数量;我们一般只用一个后台缓冲区来实现双缓存。当然,使用两个后台缓冲区就可以实现三缓存
    HWND OutputWindow; 				// 将要渲染到的窗口的句柄
    BOOL Windowed; 					// 当设为true时,程序以窗口模式运行;当设为false时,程序以全屏(full-screen)模式运行
    DXGI_SWAP_EFFECT SwapEffect; 	// 设为DXGI_SWAP_EFFECT_DISCARD,让显卡驱动程序选择最高效的显示模式
    UINT Flags; 					// 可选,通常设为0
} DXGI_SWAP_CHAIN_DESC;

描述交换链的DXGI_SWAP_CHAIN_DESC结构体示例代码:

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;

注意:如果需要在运行时改变多重采样的设置,那么必须销毁然后重新创建交换链。

注意:因为大多数显示器不支持超过24位以上的颜色,再多的颜色也是浪费,所以我们将后台缓冲区的像素格式设置为DXGI_FORMAT_R8G8B8A8_UNORM(红、绿、蓝、alpha各8位)。额外的8位alpha并不会输出在显示器上,但在后台缓冲区中可以用于特定的用途。

2.4 创建交换链

交换链(IDXGISwapChain)是通过IDXGIFactory实例的IDXGIFactory::CreateSwapChain方法创建的:

HRESULT IDXGIFactory::CreateSwapChain(
    IUnknown *pDevice , // 指向ID3D11Device的指针
    DXGI_SWAP_CHAIN_DESC *pDesc, // 指向一个交换链描述的指针
    IDXGISwapChain **ppSwapChain); // 返回创建后的交换链

我们可以通过CreateDXGIFactory(需要链接dxgi.lib)获取指向一个IDXGIFactory实例的指针。但是使用这种方法获取IDXGIFactory实例,并调用IDXGIFactory::CreateSwapChain方法后,会出现如下的错误信息:

DXGI Warning: IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory. 

要避免这个错误,我们需要使用创建设备的那个IDXGIFactory实例,要获得这个实例,必须使用下面的COM查询(具体解释可参见IDXGIFactory的文档):

IDXGIDevice *  dxgiDevice  =  0;
HR(md3dDevice ->QueryInterface(__uuidof(IDXGIDevice),
    (void**)&dxgiDevice ));
IDXGIAdapter* dxgiAdapter  =  0;
HR(dxgiDevice ->GetParent(__uuidof(IDXGIAdapter),
    (void**))&dxgiAdapte r ));
// 获得IDXGIFactory 接口
IDXGIFactory*  dxgiFactory  =  0;
HR(dxgiAdapter->GetParent(__uuid of(IDXGIFactory),
    (void**))&dxgiFactor y));
// 现在,创建交换链
IDXGISwapChain*  mSwapChain;
HR(dxgiFactory->CreateSwapChain(md3dDevice, &sd , &mSw ap Chain);
// 释放COM接口
ReleaseCOM (dxgiDevice ;
ReleaseCOM (dxgiAdapter);
ReleaseCOM (dxgiFactory);

注意:也可以使用D3D11CreateDeviceAndSwapChain方法同时创建设备、设备上下文和交换链。

注意:DXGI(DirectX Graphics Inf rastructure)是独立于Direct3D的API,用于处理与图形关联的东西,例如交换链等。DXGI与Direct3D分离的目的在于其他图形API(例如Direct2D)也需要交换链、图形硬件枚举、在窗口和全屏模式之间切换,通过这种设计,多个图形API都能使用DXGI API。

2.5 创建渲染目标视图

我们必须为资源创建资源视图,然后把资源视图绑定到不同的管线阶段。尤其是在把后台缓冲区绑定到管线的输出合并器阶段时(使Direct3D可以在后台缓冲区上执行渲染工作),我们必须为后台缓冲区创建一个渲染目标视图(render target view)。下面的代码说明了创建渲染目标视图过程:

ID3D11RenderTargetView* mRenderTargetView;
ID3D11Texture2D* backBuffer;
// 获取一个交换链的后台缓冲区指针
mSwapChain->GetBuffer(0,__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer));
// 创建渲染目标视图
md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView);
// 每调用一次GetBuffer方法,后台缓冲区的COM引用计数就会递增一次。我们需要在使用完之后释放它
ReleaseCOM(backBuffer);

2.6 创建深度/模板缓冲区及视图

创建缓冲区要即创建一个2维纹理,ID3D11Texture2D,创建它需要先给出描述D3D11_TEXTURE2D_DESC。定义如下:

typedef struct D3D11_TEXTURE2D_DESC {
    UINT Width; 					// 纹理的宽度,单位为纹理元素(texel)
    UINT Height; 					// 纹理的高度,单位为纹理元素(texel)
    UINT MipLevels; 				// 多级渐近纹理层(mipmap level)的数量。对于深度/模板缓冲区来说,只需要一个多级渐近纹理层
    UINT ArraySize; 				// 在纹理数组中的纹理数量。对于深度/模板缓冲区来说,我们只需要一个纹理
    DXGI_FORMAT Format; 			// 数据格式。一般为DXGI_FORMAT_D24_UNORM_S8_UINT,24位用于深度,8位用于模板
    DXGI_SAMPLE_DESC SampleDesc; 	// 多重采样数量和质量级别
    D3D10_USAGE Usage; 				// 表示纹理用途的D3D11_USAGE枚举类型成员。默认为D3D11_USAGE_DEFAULT,表示GPU对资源进行读写操作
    UINT BindFlags; 				// 指定该资源将会绑定到管线的哪个阶段。对于深度/模板缓冲区,该参数应设为D3D11_BIND_DEPTH_STENCIL
    UINT CPUAccessFlags; 			// 指定CPU对资源的访问权限。对于深度/模板缓冲区来说,该参数设为0
    UINT MiscFlags; 				// 可选的标志值,与深度/模板缓冲区无关,所以设为0
} D3D11_TEXTURE2D_DESC;

在本书中,我们会看到以各种不同选项来创建资源的例子;例如,使用不同的Usage标志值、绑定标志值和CPU访问权限标志值。但就目前来说,我们只需要关心那些与创建深度/模板缓冲区有关的标志值即可,其他选项可以以后再说。

另外,在使用深度/模板缓冲区之前,我们必须为它创建一个绑定到管线上的深度/模板视图。过程与创建渲染目标视图的过程相似。下面的代码示范了如何创建深度/模板纹理以及与它对应的深度/模板视图:

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, 						// 一个指向初始化数据的指针,用来填充纹理。对于深度/模板缓冲区,不需要为它填充任何初始化数据
							// 当执行深度缓存和模板操作时,Direct3D会自动向深度/模板缓冲区写入数据
	&mDepthStencilBuffer
	)); 

HR(md3dDevice->CreateDepthStencilView(
    mDepthStencilBuffer,
	0, 	// 描述资源元素数据类型(格式)。如果资源是一个有类型的格式(非typeless),这个参数可以为空值,
		// 表示创建一个资源的第一个mipmap等级的视图(深度/模板缓冲也只能使用一个 mipmap等级)。因为我们指定了深度/模板缓冲的格式,所以将这个参数设置为空值。
	&mDepthStencilView
	));

2.7 将视图绑定到输出合并器阶段

现在我们已经为后台缓冲区和深度缓冲区创建了视图,就可以将些视图绑定到管线的输出合并器阶段(output merger stage),使些资源成为管线的渲染目标和深度/模板缓冲区:

md3dImmediateContext->OMSetRenderTargets(
    1,&mRenderTargetView,mDepthStencilView);

第一个参数是我们将要绑定的渲染目标的数量;我们在这里仅绑定了一个渲染目标,不过该参数可以为着色器同时绑定多个渲染目标(是一项高级技术)。第二个参数是我们将要绑定的渲染目标视图数组中的第一个元素的指针。第三个参数是将要绑定到管线的深度/模板视图。

注意:我们可以设置一组渲染目标视图,但是只能设置一个深度/模板视图。使用多个渲染目标是一项高级技术,会在之后加以介绍。

2.8 设置视口

通常我们会把3D场景渲染到整个后台缓冲区上,但也可以把3D场景渲染到后台缓冲区的一个子矩形区域中。

我们将后台缓冲区的子矩形区域称为视口(viewport),它由如下结构体描述:

typedef struct D3D11_VIEWPORT {
    FLOAT TopLeftX; 	// 视口左上角坐标x值
    FLOAT TopLeftY; 	// 视口左上角坐标y值
    FLOAT Width; 		// 视口宽度
    FLOAT Height; 		// 视口高度
    FLOAT MinDepth; 	// 深度缓冲区最小值(0.f~1.f),默认0.f
    FLOAT MaxDepth; 	// 深度缓冲区最大值(0.f~1.f),默认1.f
} D3D11_VIEWPORT;

在填充了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, 		// 绑定的视图的数量(可以使用超过1的数量用于高级的效果)
	&vp		// 指向一个viewports的数组
	);

2.9 绘制

到了这个位置,D3D11初始化工作就完成了。在窗口绘制函数中,使用D3D11的函数绘制窗口背景。

代码如下:

void Render()
{
	// 绘制青色背景
	XMVECTORF32 color = {0.f, 1.f, 1.f, 1.0f};
	g_deviceContext->ClearRenderTargetView(g_renderTargetView,reinterpret_cast<float*>(&color));
	g_deviceContext->ClearDepthStencilView(g_depthStencilView,D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.f,0);

	// 正式的场景绘制工作

	// 显示
	g_swapChain->Present(0,0);
}

显示效果如下:

在这里完整代码代码就不贴出了,有兴趣的朋友可以点击此处下载Demo源码,Demo源码是1_D3DInit文件。

三、结语

D3D11的整个初始化过程就结束了。

我们在这篇文章中,我们从一个空的Win32项目开始,将D3D11的整个初始化过程放到了程序中,一步一步搭建出来一个空白窗口程序。这就是一个最简单的、完整的D3D程序了。

在之后的学习中,我们将在这个最简单的D3D窗口程序中,一步一步添加新代码,由简入繁地实现越来越漂亮的场景。

时间: 2024-10-12 22:59:01

Direct3D11学习:(三)Direct3D11初始化的相关文章

Jetty学习三:配置概览-需要配置什么

上一节讲述了怎么配置Jetty,这节将告诉你使用Jetty你需要配置些什么. 配置Server Server实例是Jetty服务端的中心协调对象,它为所有其他Jetty服务端组件提供服务和生命周期管理.在标准Jetty发布中,核心的服务端配置是在etc/jetty.xml文件中,你也能在其中包含其他服务端配置,可以包括: 1)ThreadPool Server实例提供了一个线程池,你可以在etc/jetty.xml中配置最大线程数和最小线程数. 2)Handlers Jetty服务端只能有一个H

ZigBee学习三 UART通信

ZigBee学习三 UART通信 本实验只对coordinator.c文件进行改动就可以实现串口的收发. 修改coordinator.c文件 byte GenericApp_TransID; // This is the unique message ID (counter) afAddrType_t GenericApp_DstAddr; unsigned char uartbuf[128];/**************************************************

swift学习三:?和!理解

Swift语言使用var定义变量,但和别的语言不同,Swift里不会自动给变量赋初始值,也就是说变量不会有默认值,所以要求使用变量之前必须要对其初始化.如果在使用变量之前不进行初始化就会报错: 1 2 3 4 5 var stringValue : String   //error: variable 'stringValue' used before being initialized //let hashValue = stringValue.hashValue //            

Jquery Easy UI初步学习(三)数据增删改

第二篇只是学了加载用datagrid加载数据,数据的增删改还没有做,今天主要是解决这个问题了. 在做增删改前需要弹出对应窗口,这就需要了解一下EasyUi的弹窗控件. 摘自:http://philoo.cnblogs.com/ 我的理解,就是panel有的属性Window.dialog都有,同时保留自己的扩展属性方法 , 所以主要展示pannel的属性. Pannel 属性 名称 类型 说明 默认值 title string 显示在Panel头部的标题文字. null iconCls strin

算法学习三阶段

?? 第一阶段:练经典经常使用算法,以下的每一个算法给我打上十到二十遍,同一时候自己精简代码, 由于太经常使用,所以要练到写时不用想,10-15分钟内打完,甚至关掉显示器都能够把程序打 出来. 1.最短路(Floyd.Dijstra,BellmanFord) 2.最小生成树(先写个prim,kruscal 要用并查集,不好写) 3.大数(高精度)加减乘除 4.二分查找. (代码可在五行以内) 5.叉乘.判线段相交.然后写个凸包. 6.BFS.DFS,同一时候熟练hash 表(要熟,要灵活,代码要

Cocos2d-x 3.1.1 学习日志3--C++ 初始化类的常量数据成员、静态数据成员、常量静态数据成员

有关const成员.static成员.const static成员的初始化: 1.const成员:只能在构造函数后的初始化列表中初始化 2.static成员:初始化在类外,且不加static修饰 3.const static成员:类只有唯一一份拷贝,且数值不能改变.因此,可以在类中声明处初始化,也可以像static在类外初始化 #include <iostream> using std::cout; using std::endl; class base { public: base(int

Spark学习三:Spark Schedule以及idea的安装和导入源码

Spark学习三:Spark Schedule以及idea的安装和导入源码 标签(空格分隔): Spark Spark学习三Spark Schedule以及idea的安装和导入源码 一RDD操作过程中的数据位置 二Spark Schedule 三Idea导入spark源码 一,RDD操作过程中的数据位置 [hadoop001@xingyunfei001 spark-1.3.0-bin-2.5.0]$ bin/spark-shell --master local[2] val rdd = sc.t

mongodb学习(三)

菜鸟啊...先吐槽一下自己 一 准备工作: 1.安装服务端: 去官网下载 http://www.mongodb.org/downloads 其实也自带了客户端 shell 2.安装客户端: mongoVUE http://blog.mongovue.com/ 并不是完全免费 破解方法: http://yhv5.com/mongovue_480.html 将服务端下载下来后直接安装 我下载在D盘也安装在D盘的... 启动mongodb的服务端不需要各种命令....直接鼠标左键双击bin中的mong

c++ boost库学习三:实用工具

noncopyable 大家都知道定义一个空类的时候,它实际包含了构造函数,拷贝构造函数,赋值操作符和析构函数等. 这样就很容易产生一个问题,就是当用户调用A a(“^_^") 或者A c="^_^" 时会发生一些意想不到的行为,所以很多时候我们需要禁用这样的用法. 一种方法就是把拷贝构造函数和赋值操作符显式的定义为private,但是这样需要很多代码. 于是boost库为大家提供了一个简单的方法:只需要将类继承于noncopyable就可以了. #include "

scala学习三---文件里读取文本行

学习了scala的基本知识后,发现了scala是集函数式和指令式结合为一体的一种语言,代码更加简洁,但是对于用习惯了java的人来说,还真的不是一件易事~~ 今天学习scala脚本读取文本文件 列子如下: import scala.io.Source if(args.length>0){ for(line <- Source.fromFile(args(0)).getLines) print(line.length+" "+line) }else{ Console.err.