DirectX11设备驱动初始化
概述:首先,我们设备驱动初始化的目的是什么?在这里并不是加载初始资源,而是对3D渲染中相对固定的功能进行实现,以及对一些内存区域进行初始化。3D绘制涉及到的东西和过程和多,在其靠近显示器的部分,是采用一种比较有效率的交换链的方式实现的。通过对前缓冲和后缓冲的轮换,达到对界面轮换的效果。此外,为达到效率的提升,DirectX11中也对深度缓存和模板缓存进行了支持,这也是需要我们在初始化的时候实现的。
交换链:这个过程本身DirectX已经帮我们提供,但我们仍然需要做一些事情。交换链中的前缓冲和后缓冲实际上是一个个2D的纹理,我们需要对其格式和大小进行设定。除此之外,交换链本身改按照什么样的规则进行交换,已经显示涉及到的多重采样标准的指定,显示的模式等,这些都需要我们对其进行描述。(个人觉得这里只涉及性能的获取,而不是性能检测)
深度缓存:Z缓存的原理是比较z轴的大小来判断谁离视点更加,然后对离视点进的像素进行缓存。它跟模板缓存保存在同一个2D纹理结构上面。
整个初始化的过程,就是对一系列的结构进行描述,然后传递给创建方法,创建出我们需要的对象出来。在整个初始化过程中,我们希望得到的主要对象有:IDXGISwapChain ,ID3D11Device ,ID3D11DeviceContext。以及D3D11_VIEWPORT
交换链的描述:
1.DXGI_SWAP_CHAIN_DESC(交换链描述体):
- typedef struct DXGI_SWAP_CHAIN_DESC {
- DXGI_MODE_DESC BufferDesc;
- DXGI_SAMPLE_DESC SampleDesc;
- DXGI_USAGE BufferUsage;
- UINT BufferCount; // 交换链的缓存数,一般为1
- HWND OutputWindow; // 后缓冲输出的窗口句柄
- BOOL Windowed; // 设置全屏或者窗口模式
- DXGI_SWAP_EFFECT SwapEffect;
- UINT Flags;
- } DXGI_SWAP_CHAIN_DESC;
1.1DXGI_MODE_DESC :指定后缓冲的显示模式。
- typedef struct DXGI_MODE_DESC {
- UINT Width; // 宽度 <span style="color: #ff0000;">如果不设置,会默认跟当前窗体进行关联</span>
- UINT Height; // 高度 <span style="color: #ff0000;">如果不设置,会默认跟当前窗体进行关联</span>
- DXGI_RATIONAL RefreshRate;
- DXGI_FORMAT Format;
- DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 光栅时,扫面的顺序,一般默认
- DXGI_MODE_SCALING Scaling;
- } DXGI_MODE_DESC;
1.1.1 DXGI_RATIONAL :指定显示适配器刷新屏幕的速率。这个地方指定的是前缓冲提交到显示器的频率,这个频率不是显示器自身的刷新频率。如果,我们把这个频率设置为跟显示器频率相同,则前缓冲总是等显示器刷新完成后才提交,这也是所谓的垂直同步。如果我们设Numerator=0,Denominator=1,则不保持垂直同步,前缓冲将不会等待,会不停的提交,这样可能会出现不好的显示界面。
- typedef struct DXGI_RATIONAL {
- UINT Numerator; // 分子
- UINT Denominator; // 分母
- } DXGI_RATIONAL;
1.1.2 DXGI_FORMAT:这是一个枚举类型,里面的成员有很多,这里主要说说它的作用。无论是前缓冲还是后缓冲还是纹理之类的,它是怎么描述一副画面的呢?首先它是一个连续的内存区(数组),这个区域记录着界面每个像素的颜色,而DXGI_FORMAT描述的是记录像素颜色的格式。例如,DXGI_FORMAT_R8G8B8A8_UNORM,它表示记录像素颜色的是一个32位的内存区,这个区域分为4个块,记录着R,G,B的颜色值和A(透明度),并且对值进行归一,就是把值转换到(0-1)的区间。注意:为保持一致性,尽量在多个地方使用相同的DXGI_FORMAT。
1.1.3 DXGI_MODE_SCALING :选定屏幕分辨与图像分辨率不一致时的策略。
- typedef enum DXGI_MODE_SCALING {
- DXGI_MODE_SCALING_UNSPECIFIED = 0, // 未指定方式,不清楚长啥样 - -。
- DXGI_MODE_SCALING_CENTERED = 1, // 始终显示在屏幕中央
- DXGI_MODE_SCALING_STRETCHED = 2 // 进行缩放
- } DXGI_MODE_SCALING;
1.2 DXGI_SAMPLE_DESC : 这个结构描述的是多重采样,注意如果采样多重采样抗锯齿,则所有绑定的渲染目标和深度缓冲区必须具有相同的采样数量和质量水平。在使用多重采样前,需要先对硬件进行验证,通过函数:ID3D11Device :: CheckMultisampleQualityLevels。
- typedef struct DXGI_SAMPLE_DESC {
- UINT Count; // 计数 1,4,8 至少为1
- UINT Quality; // 质量 为0时不进行多重采样
- } DXGI_SAMPLE_DESC;
1.3 DXGI_USAGE :一个枚举类型,使用表面的方式。我们知道交换链中的每个缓冲都实际上是一个表面,而表面并不一定只为交换链服务,所以在使用前设置其使用方式。在这里,因为我们是用来做交换链中的缓冲,所以设置为:DXGI_USAGE_RENDER_TARGET_OUTPUT(使用作为输出渲染目标的表面或资源)。
1.4 DXGI_SWAP_EFFECT :指定SwapChain 的工作方式。
- typedef enum DXGI_SWAP_EFFECT {
- DXGI_SWAP_EFFECT_DISCARD = 0, // 后缓冲的内存显示后,清理掉其内容
- DXGI_SWAP_EFFECT_SEQUENTIAL = 1, // 暂时不清楚如何使用
- DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL = 3 // 暂时不清楚如何使用
- } DXGI_SWAP_EFFECT;
1.5 Flags :它的值来源于 DXGI_SWAP_CHAIN_FLAG 枚举类型中,用于控制交换链的行为。
- typedef enum DXGI_SWAP_CHAIN_FLAG {
- DXGI_SWAP_CHAIN_FLAG_NONPREROTATED = 1, // 关闭自动图像旋转,只能在全屏模式
- DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH = 2, //全屏时自动改变显示模式适用窗口尺寸
- DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE = 4,
- DXGI_SWAP_CHAIN_FLAG_RESTRICTED_CONTENT = 8,
- DXGI_SWAP_CHAIN_FLAG_RESTRICT_SHARED_RESOURCE_DRIVER = 16,
- DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY = 32
- } DXGI_SWAP_CHAIN_FLAG;
总结:一个DXGI_SWAP_CHAIN_DESC描述的是在创建一个交换链时需要用到的各种描述,其中最重要的就是对交换面结构的描述DXGI_MODE_DESC,通过对宽度高度存储格式进行描述,可以开辟出对的空间来作为面。 而像刷新频率,多重采样,全屏状态,链中面的个数等就都属于功能相关的了。
创建交换链:
1.D3D11CreateDeviceAndSwapChain:这个方法会同时吧ID3D11Device和 ID3D11DeviceContext 创建出来,除此之外还有单独创建它们的API。调用这个方法的时候,会产生一个新的IDXGIFactory实例,以至于我们需要重新获取IDXGIFactory对象,如果我们要调用MakeWindowAssociation来屏蔽全屏快捷键。
- HRESULT D3D11CreateDeviceAndSwapChain(
- _In_ IDXGIAdapter *pAdapter, // 填写null将使用默认适配器,最好如此避免异常情况
- _In_ D3D_DRIVER_TYPE DriverType,
- _In_ HMODULE Software, // 如果驱动模式为软件,需要用到这个参数指定软件dll
- _In_ UINT Flags, // <span style="color: #ff0000;">这个不懂</span>
- _In_ const D3D_FEATURE_LEVEL *pFeatureLevels, // 传入特征水平,是个数组吗?
- _In_ UINT FeatureLevels,
- _In_ UINT SDKVersion, // SDK版本,使用D3D11_SDK_VERSION。
- _In_ const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, // <span style="color: #ff0000;">这个重要,上面说的很详细了</span>
- _Out_ IDXGISwapChain **ppSwapChain,
- _Out_ ID3D11Device **ppDevice,
- _Out_ D3D_FEATURE_LEVEL *pFeatureLevel, // 获取特征水平
- _Out_ ID3D11DeviceContext **ppImmediateContext
- );
FeatureLevels :设备功能级别,由于DirectX的版本比较多,为了让DirectX11能够在多个版本里面使用就出现了这样一个级别。在这里只是指定当前程序能够支持那些级别,在使用驱动对象的时候会有一个验证当前级别的过程,这样可以解决客户机良莠不齐的状况。http://www.cnblogs.com/gongminmin/archive/2011/8/9.html
1.1 D3D_DRIVER_TYPE :驱动类型。一般选择D3D_DRIVER_TYPE_HARDWARE
- typedef enum D3D_DRIVER_TYPE {
- D3D_DRIVER_TYPE_UNKNOWN = 0,
- D3D_DRIVER_TYPE_HARDWARE = ( D3D_DRIVER_TYPE_UNKNOWN + 1 ), // 硬件驱动
- D3D_DRIVER_TYPE_REFERENCE = ( D3D_DRIVER_TYPE_HARDWARE + 1 ), // REF模式,软件驱动
- D3D_DRIVER_TYPE_NULL = ( D3D_DRIVER_TYPE_REFERENCE + 1 ),
- D3D_DRIVER_TYPE_SOFTWARE = ( D3D_DRIVER_TYPE_NULL + 1 ), //软件驱动
- D3D_DRIVER_TYPE_WARP = ( D3D_DRIVER_TYPE_SOFTWARE + 1 )
- } D3D_DRIVER_TYPE;
1.2 IDXGISwapChain :一个交换链对象。
GetBuffer:访问一个交换链的后台缓冲区。
GetContainingOutput:获取当前的Output对象。
ResizeBuffers:更改缓冲区的大小,当窗体大小改变时需要用到它改变后缓冲的大小
ResizeTarget:传入的参数是DXGI_MODE_DESC,可以用这个方法改变窗体的大小使用代码的方式,当然还可以改变其他的内容。
SetFullscreenState:设置是否全屏。
1.3 ID3D11Device :这个驱动对象,我们后续所做的很多事情都是需要它提供相应的能力给我们的。
1.4 ID3D11DeviceContext :跟ID3D11Device的意义类似,之所以分成2个类,主要是考虑到多线程。
全屏和改变窗体的大小:
参考:http://msdn.microsoft.com/en-us/library/windows/desktop/bb205075(v=vs.85).aspx
1.如果窗体大小被改变,DXGI会帮助前缓冲进行自适应的转变,来满足显示的要求。自适应策略由DXGI_MODE_SCALING提供。这个时候我们需要关注的是后缓冲需要及时进行改变,最好的方式是在应用程序监听到WM_SIZE事件的时候对后缓冲的大小进行设置,因为这个时候我们也正好能够获取到窗体的size。
2.关于全屏和窗体的切换,也可以通过1的方式改变后缓冲的大小,但是,由于全屏时如果帧频率跟显示器的保持一致就能提高前后缓冲的交换效率(直接交换而不是采用复制的方式)。所以这个时候同时需要考虑是否需要对刷新频率进行改变。
以下是官方提供的例子:
- case WM_SIZE:
- if (g_pSwapChain)
- {
- g_pd3dDeviceContext->OMSetRenderTargets(0, 0, 0);
- // Release all outstanding references to the swap chain's buffers.
- g_pRenderTargetView->Release();
- HRESULT hr;
- // Preserve the existing buffer count and format.
- // Automatically choose the width and height to match the client rect for HWNDs.
- hr = g_pSwapChain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
- // Perform error handling here!
- // Get buffer and create a render-target-view.
- ID3D11Texture2D* pBuffer;
- hr = g_pSwapChain->GetBuffer(0, __uuidof( ID3D11Texture2D),
- (void**) &pBuffer );
- // Perform error handling here!
- hr = g_pd3dDevice->CreateRenderTargetView(pBuffer, NULL,
- &g_pRenderTargetView);
- // Perform error handling here!
- pBuffer->Release();
- g_pd3dDeviceContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL );
- // Set up the viewport.
- D3D11_VIEWPORT vp;
- vp.Width = width;
- vp.Height = height;
- vp.MinDepth = 0.0f;
- vp.MaxDepth = 1.0f;
- vp.TopLeftX = 0;
- vp.TopLeftY = 0;
- g_pd3dDeviceContext->RSSetViewports( 1, &vp );
- }
- return 1;
创建深度缓存 :
深度缓存包括Z缓存和模板缓存。
1.D3D11_TEXTURE2D_DESC :这是一个2D纹理的描述结构,看来微软始终是走不出面向过程,做什么都先整个结构设置一堆参数先。这个结构并不是描述深度缓存的,而是描述2D纹理的。
- typedef struct D3D11_TEXTURE2D_DESC {
- UINT Width; // 纹理的宽度,单位为像素
- UINT Height; // 纹理的高度,单位为像素
- UINT MipLevels;
- UINT ArraySize; // 纹理阵列中的纹理数
- DXGI_FORMAT Format; // 描述像素用的,所有的纹理格式应该保持一致。
- DXGI_SAMPLE_DESC SampleDesc; // 它指定纹理多重采样参数,应该保持一致。
- D3D11_USAGE Usage; // 标识的质地是如何被读出和写入
- UINT BindFlags;
- UINT CPUAccessFlags;
- UINT MiscFlags;
- } D3D11_TEXTURE2D_DESC;
由于纹理不是本节重点,并不把所以的字段解释清楚,留待后面学习纹理的时候再详细描述这个结果体。
注意:深度缓存的创建,其实就是创建一个2D的纹理,只不过这个纹理的格式为DXGI_FORMAT_D24_UNORM_S8_UINT,这里S8是留给深度模板的。
2.D3D11_DEPTH_STENCIL_DESC :模板缓存在很多地方都有比较好的效果,动态模糊、阴影体之类的,这里暂不详述。
- typedef struct D3D11_DEPTH_STENCIL_DESC {
- BOOL DepthEnable;
- D3D11_DEPTH_WRITE_MASK DepthWriteMask;
- D3D11_COMPARISON_FUNC DepthFunc;
- BOOL StencilEnable;
- UINT8 StencilReadMask;
- UINT8 StencilWriteMask;
- D3D11_DEPTH_STENCILOP_DESC FrontFace;
- D3D11_DEPTH_STENCILOP_DESC BackFace;
- } D3D11_DEPTH_STENCIL_DESC;