- Published on
DX学习笔记(二):DX初始化
- Authors

- Name
- 东哥
Direct3D初始化
预备知识
组件对象模型
组件对象模型***(Component Object Model,COM)***:不受DirectX语言束缚,并且向后兼容的技术
- 获得COM接口需要借助特定函数,而不是C++的new
- 删除COM有Release方法,而不是delete
Mirrosoft::WRL::ComPtr类是Window是下的COM对象的智能指针- 当ComPtr出作用域时,它会自动调用Release方法
//Get: 返回一个指向此底层COM接口的指针,此方法常用于把原始COM接口指针作为参数传给函数
`ComPtr<ID3D12RootSignature>` mRootSignature;
...
//SetGraphicsRootSignature需要获取ID3D12RootSignature*类型的参数
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
//GetAddressOf:返回指向此底层COM接口指针的地址,此函数可以利用函数参数返回COM接口的指针
`ComPtr<ID3D12CommandAlllocator>` mDirectCmdListAlloc;
...
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.GetAddressOf());
//Reset:将此ComPtr实例设置为nullptr释放与之相关的所有引用,同时减少COM接口引用次数,此方法功能与将ComPtr目标实例赋值nullptr效果相同
纹理格式
2D纹理是一种由数据元素构成的矩阵,每个元素存储的都是一个像素的颜色
-
DXGI_FORMAT_R32G32B32_FLOAT:每个元素由2个32位无符号整数分量构成,存储的不一定是颜色信息
-
DXGI_FORMAT_R16G16B16A16_UNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,1]**的区间
-
DXGI_FORMAT_R32G32_UINT:每个元素由2个32位无符号整数分量构成
-
DXGI_FORMAT_R8G8B8A8_UNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,1]**的区间
-
DXGI_FORMAT_R8G8B8A8_SNORM:每个元素由4个8位无符号分量构成,每个分量都被映射到**[-1,1]**的区间
-
DXGI_FORMAT_R9G9B9A9_SINT:每个元素由4个8位无符号分量构成,每个分量都被映射到**[-128,127]**的区间
-
DXGI_FORMAT_R8G8B8A8_UINT:每个元素由4个8位无符号分量构成,每个分量都被映射到**[0,255]**的区间
其他格式
- DXGI_FORMAT_R16G16B16A16_TYPELESS:每个元素由4个16位无符号分量构成,但是没有指出数据类型
交换链和页面翻转

- 前台缓冲区和后台缓冲区在绘制渲染过程中互换,这种操作称为:呈现(presenting,亦有译作提交、显示)
- 前后台缓冲区构成了交换链,Direct3D中用==
IDXGISwapChain==接口来表示 - 这个接口不仅储存了前后台缓冲区的纹理,还提供了修改缓冲区大小(IDXGISwapChain::ResiezeBuffers)和呈现缓冲区内容(IDXGISwapChain::Present)的方法
- 使用2个缓冲区的情况称为双缓冲(double buffering,亦有译作双重缓冲、双倍缓冲等)
- 还可以用更多的缓冲区,使用3个缓冲区就叫作三重缓冲(triple buffering)
深度缓冲
深度缓冲区(depth buffer):存储的非图像数据,而是特定像素的深度信息
- 深度值范围 0.0-1.0
- 0.0代表观察者在视锥体(视域体、视景体、视截体、视体),即观察者能看到的空间范围
- 1.0代表观察者在视锥体中嫩通过看到的离自己最远的像素
- 如果后台缓冲区的分辨率位1280x1024,那么深度缓冲去也应当由1280x1024
深度缓冲区的原理:计算每个像素的深度值,并执行深度测试(depth test),具有最小深度值的像素会最终写入后台缓冲
深度缓冲区也是一种纹理,用如下格式来创建
- DXGI_FORMAT_D32_FLOAT_S8X24_UINT:占用64位,取其中的32位指定一个浮点型深度缓冲区,另有8位无符号整数分配给模板缓冲区(stencil buffer),并且将该元素映射到[0,255]
- DXGI_FORMAT_D32_FLOAT:指定一个32位浮点型深度缓冲区
- DXGI_FORMAT_D24_UNORM_指定一个无符号的24位深度缓冲区,并将该元素映射到[0,1]区间;另有8位无符号整数分配给模板缓冲区(stencil buffer),并且将该元素映射到[0,255]
- DXGI_FORMAT_D16_UNORM:指定一个16位浮点型深度缓冲区,并将该元素映射到[0,1]区间
资源与描述符
资源->中间层(即描述符)->GPU
描述符
- 一种把送往GPU的资源进行描述的轻量级结构;
- 绘制所需的资源通过描述符绑定到渲染流水线上;
- 为GPU解释资源,如告知Direct3D某个资源如何使用(绑定到流水线的哪个阶段);
- 指定欲绑定资源中的局部数据
常见描述符
- CBV/SRV/UAV :分表表示常量缓冲区(constant buffer view)、着色器资源视图(shader resource view)和无序访问试图(unordered access view)3种资源;
- 采样器(sampler,亦有译作取样器):表示采样器资源(用于纹理)
- RTV:渲染目标视图资源(render target view)
- DSV:深度/模板视图资源(depth/stencil view)
描述符堆:存放某种特定类描述符的内存,可以看作是描述符数组
多重采样
超级采样(SSAA)
超级采样:反走样技术
- 使用4倍于屏幕分辨率大小的后台缓冲区和深度缓冲去;
- 3D场景以这种更大的分辨率渲染到后台缓冲区中;
- 当数据要从后台缓冲区调往屏幕显示的时候,会将后台缓冲区按4个像素一组进行解析(降采样,downsample),把放大的采样点数降低回原来采样点数每组用求平均值的方法得到相对平滑的像素颜色
- 实际上是通过软件的方式提升了画面分辨率
- 超级采样是高开销的操作,因为限速处理数量和占用内存大小都增加到了4倍,因此Direct3D支持一种性能和效果折中的反走样技术:多重采样(multisampling),记作MSAA
多重采样(MSAA)
- 多重采样不需要对每个子像素都进行计算
- 而是仅计算一次像素中心的颜色,在基于可视性和覆盖性将得到的颜色信息分享给其子像素
区别
-
超级采样:图像颜色要根据每一个像素来计算,因此每个子像素都可以各具不的颜色;开销更大但是更精确
-
多重采样:每个像素只需要计算一次,最后假尼姑得到的颜色数据复制到多边形覆盖的所有可见子像素中
用Direct3D进行多重采样
typedef struct DXGI_SAMPLE_DESC
{
UINT count;//指定了每个像素的采样次数,采样次数越多,代价越高
UINT Quality;;//指示用户期望的图像质量级别,不同厂家而言,这个参数相差很多
}
根据给定的纹理格式和采样数量,用ID3D12Device::CheckFeatureSupport方法查询对应的质量级别
- 考虑到多重采样会占用内存资源,又为了保证程序性能等原因,通常会把采样数量设定位 4 或 8
- 如果不希望使用多重采样,可以设置采样数量位1,质量设置为0
功能级别
Direct3D 11开始引用了功能级别(feature level),代码里用 D3D_FEATURE_LEVEL表示
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
}
- 功能级别为不同级别所支持的功能进行严格界定
DirectX图形学基础结构
DirectX图形学基础结构(DXGI)是一种与Direct3D配合使用的API
如,IDXGIFactory是DXGI中的关键接口之一,用于创建IDXGISwapChain接口以及枚举显示适配器
一个系统可能由数个显示设备,我们称每一台显示设备都是一个显示输出,用IDXGIOutput接口来表示
功能支持的检测
ID3D12Device::CheckFeatureSupport方法是检测当前图形驱动对多重过采样的支持,圆形如下
HRESULT ID3D12Device::CheckFeatureSupport(D3D12_FEATURE Feature,void* pFeatureSuportData,UINT FeatureSupportDataSize);
-
***Feature:***枚举类型
D3D12_FEATURE中的成员之一,用于指定我们希望检测的功能支持类型,具体如下- D2D12_FEATURE_D3D12_OPTIONS:检测当前图形驱动对Direct3D 12各种功能的支持情况
- D3D12_FEATURE_ARCHITECTURE:检测图形适配器中GPU的硬件体系架构特性
- D3D12_FEATURE_FEATURE_LEVELS:检测对功能级别的支持情况
- D3D12_FEATURE_FORMAT_SUPPORT:检测对给定纹理格式的支持情况
- D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS:检测对多重采样功能的支持情况
-
pFeatureSuportData:指向某种数据结构的指针,该结构中存有检索到的特定功能支持的信息,此结构体的具体类型取决于Feature参数
- D3D12_FEATURE_D3D12_OPTIONS:返回一个D3D12_FEATURE_DATA_D3D12_OPTIONS实例
- D3D12_FEATURE_ARCHITECTURE:返回D3D12_FEATURE_ARCHITECTURE实例
- D3D12_FEATURE_FEATURE_LEVELS:同上类推
- D3D12_FEATURE_FORMAT_SUPPORT:同上类推
- D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS:同上类推
-
FeatureSupportDataSize:传回pFeatureSuportData参数中的数据结构大小
资源驻留
Direct3D12中,应用程序通过控制资源在显存中的去留,主动管理资源的驻留情况(Direct3D11中则有系统自动管理)
一般情况,资源创建时就会驻留在显存中,被销毁时则清出。但是通过下面方法我们可以自己管理资源的驻留
HRESULT ID3D12Device::MakeResident(UINT NumObjects,ID3D12Pageable* const *ppObjects);
HRESULT ID3D12Device::Evict(UINT NumObjects,ID3D12Pageable* const *ppObjects);
这两个方法的第二个参数都是ID3D12Pageable资源数组,第一个参数表示该数组资源的数量
CPU和GPU的交互
- 每个GPU都至少维护这一个命令队列(command queue,本质上是环形缓冲区,即ring buffer)。
- 借助Direct3D API,CPU可以用命令列表(command list)将命令提交到这个队列中去
- 新加入的命令不会立即执行
- 假如命令列表空空如也,那么GPU会闲置
- 假如命令列表填满,那么CPU会在某个时刻保持空闲
在Direct3D 12中,命令队列被抽象为==ID3D12CommandQueue==接口来表示,通过填写D3D12_COMMAND_QUEUE_DESC结构体来表示队列,在通过调用ID3D12Device::CreateCommandQueue方法来创建。
命令队列
创建命令队列
//创建队列智能指针
Microsoft::WRL::`ComPtr<ID3D12CommandQueue>` mCommandQueue;
...;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
//创建队列方法
//IID_PPV_ARGS辅助宏本质是将ppType强制转换为void**类型
//Direct3D 12中创建接口实例的API时,大多数都有一个参数是类型void**的待创接口COM ID
ThrowIfFailed(md3dDevice>CreateCommandQueue(&queueDesc,IID_PPV_ARGS(&mCommandQueue)));
...;
//添加命令到命令列表里
//第一个参数是待执行的命令列表数组的数量
//第二个参数是待执行的命令列表数组
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
//下列两个方法不是执行命令,而是将命令添加到命令列表里,还是需要通过ExecuteCommandLists方法才将命令真正加入到命令列表
mCommandQueue->DrawIndexedInstanced(36,1,0,0,0);
mCommandQueue->RSSetViewports(1,&mScreenViewport);
//结束记录命令
//必须在调用ExecuteCommandLists方法之前先关闭
mCommandQueue->Close();
内存分配器
内存分配器:存储命令列表里的命令,执行ID3D12CommandQueue::ExecuteCommandLists方法时,命令队列就会引用分配器里的命令
virtual HRESULT STDMETHODCALLTYPE CreateCommandAllocator(
_In_ D3D12_COMMAND_LIST_TYPE type,
REFIID riid,
_COM_Outptr_ void **ppCommandAllocator) = 0;
type:命令列表类型D3D12_COMMAND_LIST_TYPE_DIRECT:GPU可直接执行的命令D3D12_COMMAND_LIST_TYPE_BUNDLE:打包的命令列表,一般不用
riid:适配接口的COM IDppCommandAllocator:输出指向所建命令分配器的指针
//内存管理指针
Microsoft::WRL::`ComPtr<ID3D12CommandAllocator>` mDirectCmdListAlloc;
...;
//第一个参数:此命令分配器相关联的命令列表类型,具体见下图
//第二个参数:内存分配器地址
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
创建命令列表
//原型
virtual HRESULT STDMETHODCALLTYPE CreateCommandList(
_In_ UINT nodeMask,
_In_ D3D12_COMMAND_LIST_TYPE type,
_In_ ID3D12CommandAllocator *pCommandAllocator,
_In_opt_ ID3D12PipelineState *pInitialState,
REFIID riid,
_COM_Outptr_ void **ppCommandList) = 0;
//实例
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // Associated command allocator
nullptr, // Initial PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));
- nodeMask:如果只有1个GPU,设置成0;多个GPU用于关联的物理GPU
- type:命令列表类型
- pCommandAllocator:所建命令列表关联的命令分配器,类型必须匹配
- pInitialState:指定命令列表的渲染流水线初始状态,一般可以设置为
nullptr - riid:待创建的
ID3D12CommandList接口的COM ID - ppCommandList:输出指向所建命令列表的指针
小结
- 可以创建多个关联同一个命令分配器的命令列表
- 但是不能同时用他们记录命令
- 其中一个命令列表在记录命令时,必须关闭同一分配器的其他命令列表
- 要保证命令列表中的所有命令都会按顺序连续的添加到命令分配器内
- 当创建或重置一个命令列表时,它会处于“打开 ”状态,所以同时为同一命令列表分配器创建两个命令列表会报错
ID3D12CommandQueue::ExecuteCommandList(C);//把命令添加到命令列表
/*将命令列表恢复到初始状态,借此继续复用其底层内存;
重置列表不会影响命令队列的命令,内存分配器在当中维护
*/
ID3D12GraphicsCommandLIst::Reset();
//GPU提交了一整帧的渲染命令后,我们可能还要为了绘制下一帧服用命令分配器的内存
//注意:在没有确定GPU执行完命令分配器的所有命令之前,千万不要重置命令分配器
ID3D12CommandAllocator::Reset();
GPU与CPU的同步
刷新命令队列:CPU会等待GPU完成所有命令处理,直到到达指定的***围栏点(fence point)***为止。
创建围栏
HRESULT ID3D12Device::CreateFence(UINT64 InitialValue,
D3D12_FENSE_FLAGS Flags,
REFIID riid,
void** ppFence);
//示例
Microsoft::WRL::`ComPtr<ID3D12Fence>` mFence;
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&mFence)));
void D3DApp::FlushCommandQueue()
{
// 增加围栏值,接下来将命令标记到此围栏点
mCurrentFence++;
// 向命令队列添加一条用来设置新围栏点的命令
//由于这条命令有交给GPU处理,所以在GPU处理完命令队列中此Signal()的所有命令之前
//它不会设置新的围栏点
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
// 在CPU等待GPU,直到后者执行完这个围栏点之前的所有命令
if(mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
// 若GPU命令中当前的围栏点(即执行到Signal()指令,修改了围栏点),则激发预定事件
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
// 等待GPU命中围栏,激发事件
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
资源转换
资源冒险:当GPU的写操作还没有完成或者还没有开始,却开始读取资源的情况
为了解决资源冒险问题,Direct3D设计了一组相关状态,如资源在创建的时候会初一默认状态,直到应用程序通过方法将其转换为另一种状态。
转换资源屏障(transition resource barrier):通过一个API调用来转换多个资源要用到的数组
初始化Direct3D
- 初始化流程
- 用
D3D12CreateDevice函数创建ID3D12Device接口实例 - 创建一个
ID3D12Fence对象,并且查询描述符的大小 - 检测用户设备对
4X MSAA质量级别的支持情况 - 一次创建命令队列、命令列表分配器和主命令列表
- 描述并创建交换链
- 创建应用程序所需的描述符堆
- 调整后台缓冲区的大小,并为它创建渲染图标视图
- 创建深度/模板缓冲区及与之关联的深度/模板视图
- 设置视口(viewport)和裁剪矩形(scissor rectangle)
创建设备
HRESULT WINAPI D3D12CreateDevice(
_In_opt_ IUnknown* pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
_In_ REFIID riid, // Expected: ID3D12Device
_COM_Outptr_opt_ void** ppDevice );
案例
// Try to create hardware device.
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // default adapter
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
pAdapter:使用的显示适配器,如果空,则使用主显示适配器MinimumFeatureLevele:应用程序需要硬件所支持的最低功能级别,如果适配器不支持此功能级别,则设备创建失败riid:ID3D12Device接口的COM IDppDevice:返回创建的Direct3D 12设备
创建失败的话会尝试创建
WARP设备
创建围栏
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE,
IID_PPV_ARGS(&mFence)));
//查询并保存描述符信息,方便后面使用
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
检测4X MSAA支持
// Check 4X MSAA quality support for our back buffer format.
// All Direct3D 11 capable devices support 4X MSAA for all render
// target formats, so we only need to check quality support.
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&msQualityLevels,
sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
创建命令队列和命令列表
-
命令队列:
ID3D12CommandQueue -
命令分配器:
ID3D12CommandAllocator -
命令列表:
ID3D12GraphicsCommandList
//.h申明
Microsoft::WRL::`ComPtr<ID3D12CommandQueue>` mCommandQueue;
Microsoft::WRL::`ComPtr<ID3D12CommandAllocator>` mDirectCmdListAlloc;
Microsoft::WRL::`ComPtr<ID3D12GraphicsCommandList>` mCommandList;
//.cpp
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(
0,
D3D12_COMMAND_LIST_TYPE_DIRECT,
mDirectCmdListAlloc.Get(), // 关联的命令分配器
nullptr, //初始的PipelineStateObject
IID_PPV_ARGS(mCommandList.GetAddressOf())));
//从关闭状态开始。 这是因为我们第一次提到
//在命令列表中,我们将对其进行重置,并且需要先关闭它
//调用Reset。
mCommandList->Close();
}
创建交换链
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;
- BufferDesc:后台缓冲区的属性,主要是高度、宽度和像素格式
- SampleDesc:多重采样的质量级别以及对每个像素的采样次数
- BufferUsage:如果要将数据渲染到后台缓冲区,则设置为
DXGI_USAGE_RENDER_TARGET_OUTPUT - BufferCount:缓冲区数量,指定为2即双缓冲
- OutputWindow:渲染窗口的句柄
- Windowed:true则窗口模式运行,否则全屏
- SwapEffect:指定为
DXGI_SWAP_EFFECT_FLIP_DISCARD - Flags:可选;如果指定为
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH则程序切换为全屏时,将选择适用于当前窗口尺寸的显示模式;否则就采用当前桌面的显示模式
DXGI_MODE_DESC
typedef struct DXGI_MODE_DESC
{
UINT Width;//缓冲区分辨率的宽度
UINT Height;//高度
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format;//显示格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;//逐行扫面vs.隔行扫描
DXGI_MODE_SCALING Scaling;//如何进行拉升
} DXGI_MODE_DESC;
执行创建交换链
void D3DApp::CreateSwapChain()
{
// 释放我们将重新创建的先前的交换链。
mSwapChain.Reset();
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 = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// Note: 交换链使用队列执行刷新。
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
创建描述符堆
创建描述符堆来存储程序中要用到的描述符/视图,本例中需要创建两个描述符堆来存储SwapChainBufferCount个RTV ,另外一个存储1个DSV
//.h
Microsoft::WRL::`ComPtr<ID3D12DescriptorHeap>` mRtvHeap;
Microsoft::WRL::`ComPtr<ID3D12DescriptorHeap>` mDsvHeap;
//.cpp
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;//static value=2
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}
创建以后需要通过方法来获得描述符的句柄
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView()const
{//构造函数根据给定的偏移量找到当前后台缓冲区的RTV
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),//堆中的首个句柄
mCurrBackBuffer,//偏移至后台缓冲区描述符句柄的索引
mRtvDescriptorSize);//描述符所占字节大小
}
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView()const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}
创建渲染目标视图
资源不能与渲染流水线中的阶段直接绑定,所以必须先为资源创建视图(描述符),并将其绑定到流水线阶段
virtual HRESULT STDMETHODCALLTYPE GetBuffer(
/* [in] */ UINT Buffer,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][out][in] */
_COM_Outptr_ void **ppSurface) = 0;
- Buffer:后台缓冲区索引
- riid:COM ID
- ppSurface:返回
ID3D12Resource接口的指针,即后台缓冲区
调用此方法后会增加计数,所以使用后需要释放,需通过ComPtr
然后获得后台缓冲区创建的渲染目标视图
virtual void STDMETHODCALLTYPE CreateRenderTargetView(
_In_opt_ ID3D12Resource *pResource,
_In_opt_ const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
_In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) = 0;
- pResource:指定用作渲染目标的资源
- pDesc:指向
D3D12_RENDER_TARGET_VIEW_DESC数组结构体的指针 - DestDescriptor:引用所创建渲染目标视图的描述符句柄
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
//获得交换链中的第 i 个缓冲区
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
//为此缓冲区创建一个RTV
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
//偏移到下一个缓冲区
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
创建深度/模板缓冲区及其视图
因为深度缓冲区就是一种2D纹理,所以我们通过填写D3D12_RESOURCE_DESC结构体来描述纹理资源
再用ID3D12Device::CreateCommittedResource方法来创建它
- D3D12_RESOURCE_DESC
typedef struct D3D12_RESOURCE_DESC
{
D3D12_RESOURCE_DIMENSION Dimension;
UINT64 Alignment;
UINT64 Width;
UINT Height;
UINT16 DepthOrArraySize;
UINT16 MipLevels;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D12_TEXTURE_LAYOUT Layout;
D3D12_RESOURCE_FLAGS Flags;
} D3D12_RESOURCE_DESC;
-
D3D12_RESOURCE_DIMENSION Dimension:资源的维度
-
enum D3D12_RESOURCE_DIMENSION { D3D12_RESOURCE_DIMENSION_UNKNOWN = 0, D3D12_RESOURCE_DIMENSION_BUFFER = 1, D3D12_RESOURCE_DIMENSION_TEXTURE1D = 2, D3D12_RESOURCE_DIMENSION_TEXTURE2D = 3, D3D12_RESOURCE_DIMENSION_TEXTURE3D = 4 } D3D12_RESOURCE_DIMENSION;
-
-
Width:像素单位的纹理宽度。对于缓冲区,此项是占用的字节数
-
Height:同上
-
DepthOrArraySize:纹素为单位的纹理深度,或者是纹理数组的大小
-
MipLevels:mipmap层级的数量
-
Format:DXGI_FORMAT枚举成员之一
-
SampleDesc:多重采样级别和每个像素的采样次数
-
Layout:D3D12_TEXTURE_LAYOUT枚举成员之一,用于指定纹理布局
-
Flags:杂项标记,对于深度/模板缓冲区资源,设置为
D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
GPU资源都存储在堆中,本质是具有特定属性的GPU显存快
ID3D12Device::CreateCommitedResource方法根据提供的属性创建一个资源和一个堆,把资源提交到这个堆
CreateCommittedResource
virtual HRESULT STDMETHODCALLTYPE CreateCommittedResource(
_In_ const D3D12_HEAP_PROPERTIES *pHeapProperties,//资源提交到的堆的属性,见下
D3D12_HEAP_FLAGS HeapFlags,//额外标记,一般是D3D12_HEAP_FLAG_NONE
_In_ const D3D12_RESOURCE_DESC *pDesc,//描述待创建的资源
D3D12_RESOURCE_STATES InitialResourceState,//此参数来设置资源的初始状态
_In_opt_ const D3D12_CLEAR_VALUE *pOptimizedClearValue,//清楚资源的优化值,不需要就选择nullptr
REFIID riidResource,//COM ID
_COM_Outptr_opt_ void **ppvResource) = 0;//新创建的资源,指向ID3D12Resource的指针
D3D12_HEAP_PROPERTIES
typedef struct D3D12_HEAP_PROPERTIES
{
D3D12_HEAP_TYPE Type;
D3D12_CPU_PAGE_PROPERTY CPUPageProperty;
D3D12_MEMORY_POOL MemoryPoolPreference;
UINT CreationNodeMask;
UINT VisibleNodeMask;
} D3D12_HEAP_PROPERTIES;
D3D12_HEAP_TYPE
enum D3D12_HEAP_TYPE
{
D3D12_HEAP_TYPE_DEFAULT = 1,//默认堆
D3D12_HEAP_TYPE_UPLOAD = 2,//上传堆
D3D12_HEAP_TYPE_READBACK = 3,//回读堆
D3D12_HEAP_TYPE_CUSTOM = 4//高级场景使用
} D3D12_HEAP_TYPE;
示例
// Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
// 使用资源格式将描述符创建为整个资源的MIP级别0。
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), nullptr, DepthStencilView());
// 将资源从其初始状态转换为深度缓冲区。
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
设置视口
D3D12_VIEWPORT mScreenViewport;
typedef struct D3D12_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;//负责从区间0-1转化为MinDepth-MaxDepth
FLOAT MaxDepth;
} D3D12_VIEWPORT;
填好结构体以后通过函数ID3D12GraphicsCommandList::RSSetViewports方法来设置视口
示例
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;
mCommandList->RSSetViewports(1, &mScreenViewport);
不能为同一个渲染目标指定多个视口
而多个视口则是一种用于对多个渲染目标同时进行渲染的高级技术
命令列表重置,视口也要重置
设置裁剪矩形
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;
mScissorRect = { 0, 0, mClientWidth, mClientHeight };
mCommandList->RSSetScissorRects(1, &mScissorRect);
不能为同一个渲染目标指定多个裁剪矩形。
多裁剪矩形是以各种用于同时对多个渲染目标进行渲染的高级技术
裁剪矩形需要随着命令列表重置而重置
计时与动画
性能计时器
QueryPerformanceCounter函数来活得性能计时器测量的当前时刻值
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1.0 / (double)countsPerSec;
通过如下方式转换为秒
valueInSecs=valueInCounts * mSecondsPercount;
调用2次
QueryPerformanceCounter函数得到两个时间戳的相对插值
__int64 A;
QueryPerformanceFrequency((LARGE_INTEGER*)&A);
__int64 B;
QueryPerformanceFrequency((LARGE_INTEGER*)&B);
B-A即可获得执行期间的计数值,或者(B-1)*mSecondsPerCount获得代码运行期间所花费的秒数
游戏计时器类
class GameTimer
{
public:
GameTimer();
float TotalTime()const; //秒为单位
float DeltaTime()const; //秒为单位
void Reset(); // 开始循环之前调用
void Start(); // 接触计时器暂停时调用
void Stop(); // 暂停计时器调用
void Tick(); //每帧都要调用
private:
double mSecondsPerCount;
double mDeltaTime;
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;
bool mStopped;
};
帧与帧之间的时间间隔
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
// 获得本帧开始的时刻
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;
// 两帧的时间差
mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;
// 把当前时间设置为下一次的开始时间
mPrevTime = mCurrTime;
//保证时间差为非负值;
//在处理器处于节能模式或者计算两次时间差的过程中切换到了另一个处理器可能会得到负值
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
次Tick函数被调用在D3DApp::Run函数
int D3DApp::Run()
{
MSG msg = {0};
mTimer.Reset();
while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();
if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}
Reset方法初始化第一帧的数据,因为第一帧没有之前的帧
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mBaseTime = currTime;
mPrevTime = currTime;
mStopTime = 0;
mStopped = false;
}
总时间
float GameTimer::TotalTime()const
{
//如果我们停止了,请勿计算自停止以来经过的时间。
//此外,如果我们之前已经停顿了一下
// mStopTime-mBaseTime包含暂停时间,我们不想计算。
//要纠正此问题,我们可以从mStopTime中减去暂停时间:
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------------*------> time
// mBaseTime mStopTime startTime mStopTime mCurrTime
if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);
}
//距离mCurrTime-mBaseTime包括暂停时间,
//我们不想计算。 要纠正这一点,我们可以减去
//从mCurrTime暂停的时间:
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------> time
// mBaseTime mStopTime startTime mCurrTime
else
{
return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);
}
}
应用程序框架示例
自己模仿框架的代码
DXApp.h
#pragma once
#include "../Common/d3dUtil.h"
#include "../Common/d3dx12.h"
#include "../Common/GameTimer.h"
#if defined(DEBUG) || defined(_DEBUG)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif
#include <dxgiformat.h>
// Link necessary d3d12 libraries.
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib, "D3D12.lib")
#pragma comment(lib, "dxgi.lib")
class DXApp
{
public:
DXApp(HINSTANCE nInstance);
DXApp(const DXApp& rhs) = delete;
DXApp& operator=(const DXApp& rhs) = delete;
virtual ~DXApp();
protected:
virtual void OnResize();
virtual bool Initialize();
virtual void CreateRtvAndDsvDescriptorHeaps();//创建描述符堆
virtual void Update(const GameTimer& gt) = 0;
virtual void Draw(const GameTimer& gt) = 0;
bool InitMainWindow();//初始化窗口
bool InitDirect3D();//初始化DX
void FlushCommandQueue();//齐平命令队列
void CreateSwapChain();//创建交换链
void CreateCommandObjects();//创建命令队列、命令适配器、命令列表
ID3D12Resource* CurrentBackBuffer()const;
D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferView()const;
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView()const;
void LogAdapters();
void LogAdapterOutputs(IDXGIAdapter* adapter);
void LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format);
public:
static DXApp* GetApp();
HINSTANCE AppInst()const;//得到应用程序实例
HWND MainWnd()const;//得到窗口
float AspectRatio()const;//长宽比
bool Get4xMsaaState()const;//4XMSAA开启与否
void Set4xMsaaState(bool value);//设置4XMSAA
int Run();
void CalculateFrameStats();
/*
HWND hwnd; //窗口句柄
UINT message; //消息常量标识符
WPARAM wParam; //32位消息的特定附加信息,具体表示什么处决于message
LPARAM lParam; //32位消息的特定附加信息,具体表示什么处决于message
*/
virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
//鼠标输入
virtual void OnMouseDown(WPARAM btnState, int x, int y) { }
virtual void OnMouseUp(WPARAM btnState, int x, int y) { }
virtual void OnMouseMove(WPARAM btnState, int x, int y) { }
protected:
static DXApp* mApp;
HINSTANCE mhAppInst = nullptr; // 应用程序实例句柄
HWND mhMainWnd = nullptr; // 主窗口句柄
bool mAppPaused = false; // 应用程序是否已暂停?
bool mMinimized = false; // 将应用程序最小化?
bool mMaximized = false; // 应用程序是否已最大化?
bool mResizing = false; // 是否拖动了大小调整栏?
bool mFullscreenState = false;// 启用全屏
// 是否启用 4X MSAA (?.1.8). The default is false.
bool m4xMsaaState = false; // 启用 4X MSAA
UINT m4xMsaaQuality = 0; // 4倍MSAA的质量水平
GameTimer mTimer;
//COM
Microsoft::WRL::`ComPtr<ID3D12Device>` md3dDevice; //设备接口
Microsoft::WRL::`ComPtr<IDXGIFactory4>` mdxgiFactory;
Microsoft::WRL::`ComPtr<IDXGISwapChain>` mSwapChain;//交换链
Microsoft::WRL::`ComPtr<ID3D12Fence>` mFence;//围栏
UINT64 mCurrentFence = 0;
Microsoft::WRL::`ComPtr<ID3D12CommandQueue>` mCommandQueue;//命令队列
Microsoft::WRL::`ComPtr<ID3D12CommandAllocator>` mDirectCmdListAlloc;//适配器
Microsoft::WRL::`ComPtr<ID3D12GraphicsCommandList>` mCommandList;//列表
//描述符
UINT mRtvDescriptorSize = 0;
UINT mDsvDescriptorSize = 0;
UINT mCbvSrvUavDescriptorSize = 0;
static const int SwapChainBufferCount = 2;//双重缓冲
int mCurrBackBuffer = 0;
Microsoft::WRL::`ComPtr<ID3D12Resource>` mSwapChainBuffer[SwapChainBufferCount];//交换链缓冲区数组
Microsoft::WRL::`ComPtr<ID3D12Resource>` mDepthStencilBuffer;//深度模板缓冲区
Microsoft::WRL::`ComPtr<ID3D12DescriptorHeap>` mRtvHeap;//RTV描述符堆
Microsoft::WRL::`ComPtr<ID3D12DescriptorHeap>` mDsvHeap;//DSV描述符堆
std::wstring mMainWndCaption = L"DX App";
D3D_DRIVER_TYPE md3dDriverType = D3D_DRIVER_TYPE_HARDWARE;
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
D3D12_VIEWPORT mScreenViewport;//视口
D3D12_RECT mScissorRect;//裁剪
int mClientWidth = 800;
int mClientHeight = 600;
};
DXApp.cpp
#include "DXApp.h"
#include "WindowsX.h"
#include <winuser.h>
#include <dxgi.h>
#include <iostream>
using Microsoft::WRL::ComPtr;
using namespace std;
using namespace DirectX;
DXApp* DXApp::mApp = nullptr;
LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//cout << "test";
return DXApp::GetApp()->MsgProc(hwnd, msg, wParam, lParam);
}
DXApp::DXApp(HINSTANCE nInstance):mhAppInst(nInstance)
{
assert(mApp == nullptr);
mApp = this;
}
DXApp::~DXApp()
{
if (mhAppInst != nullptr)
{
FlushCommandQueue();
}
}
void DXApp::OnResize()
{
assert(md3dDevice);
assert(mSwapChain);
assert(mDirectCmdListAlloc);
FlushCommandQueue();
ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(),nullptr));
//释放我们将重新创建的先前资源。
for (int i=0;i<SwapChainBufferCount;++i)
{
mSwapChainBuffer[i].Reset();
}
mDepthStencilBuffer.Reset();
//调整交换链的大小。
ThrowIfFailed(mSwapChain->ResizeBuffers(
SwapChainBufferCount,
mClientWidth,
mClientHeight,
mBackBufferFormat,
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));
mCurrBackBuffer=0;
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
//构造函数根据给定的偏移量找到当前后台缓冲区的RTV
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);//堆中的首个句柄
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
//创建深度/模板缓冲区和视口
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension= D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment=0;
depthStencilDesc.Width=mClientWidth;
depthStencilDesc.Height=mClientHeight;
depthStencilDesc.DepthOrArraySize=1;
depthStencilDesc.MipLevels=1;
depthStencilDesc.Format=mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count=m4xMsaaState?4:1;
depthStencilDesc.SampleDesc.Quality=m4xMsaaState?(m4xMsaaQuality-1):0;
depthStencilDesc.Layout=D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags=D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format=mDepthStencilFormat;
optClear.DepthStencil.Depth=1.0f;
optClear.DepthStencil.Stencil=0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
//使用资源的格式将描述符创建为整个资源的MIP级别0。
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(),nullptr, DepthStencilView());
//将资源从其初始状态转换为深度缓冲区。
mCommandList->ResourceBarrier(1,&CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));
//执行resize命令
ThrowIfFailed(mCommandList->Close());
ID3D12CommandList* cmdsLists[]={mCommandList.Get()};
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists),cmdsLists);
//等待到resize完成
FlushCommandQueue();
//更新视口变换以覆盖客户区域。
mScreenViewport.TopLeftX = 0;
mScreenViewport.TopLeftY = 0;
mScreenViewport.Width = static_cast<float>(mClientWidth);
mScreenViewport.Height = static_cast<float>(mClientHeight);
mScreenViewport.MinDepth = 0.0f;
mScreenViewport.MaxDepth = 1.0f;
mScissorRect={0,0,mClientWidth,mClientHeight};
}
bool DXApp::Initialize()
{
if (!InitMainWindow())
{
return false;
}
if (!InitDirect3D())
{
return false;
}
OnResize();
return true;
}
void DXApp::CreateRtvAndDsvDescriptorHeaps()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors=SwapChainBufferCount;
rtvHeapDesc.Type=D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags=D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask=0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvHeapDesc,IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = SwapChainBufferCount;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}
bool DXApp::InitMainWindow()
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = mhAppInst;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"MainWnd";
if (!RegisterClass(&wc))
{
MessageBox(0, L"RegisterClass Failed.", 0, 0);
return false;
}
// Compute window rectangle dimensions based on requested client area dimensions.
RECT R = { 0, 0, mClientWidth, mClientHeight };
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
int width = R.right - R.left;
int height = R.bottom - R.top;
mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(),
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);
if (!mhMainWnd)
{
MessageBox(0, L"CreateWindow Failed.", 0, 0);
return false;
}
ShowWindow(mhMainWnd, SW_SHOW);
UpdateWindow(mhMainWnd);
return true;
}
bool DXApp::InitDirect3D()
{
// {
//#if defined(DEBUG) || defined(_DEBUG)
// // Enable the D3D12 debug layer.
// {
// `ComPtr<ID3D12Debug>` debugController;
// ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
// debugController->EnableDebugLayer();
// }
//#endif
ThrowIfFailed(CreateDXGIFactory(IID_PPV_ARGS(&mdxgiFactory)));
//创建硬件设备
HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice));//第一个参数是空则使用默认显示器
//回退至WARP设备
if (FAILED(hardwareResult))
{
`ComPtr<IDXGIAdapter>` pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice)));
}
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
//描述符信息,方便以后使用
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
//检查4X MSAA质量对我们的后缓冲区格式的支持。
//所有支持Direct3D 11的设备都对所有渲染支持4倍MSAA
//目标格式,因此我们只需要检查质量支持。
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
CreateCommandObjects();
CreateSwapChain();
CreateRtvAndDsvDescriptorHeaps();
return true;
}
void DXApp::FlushCommandQueue()
{
//提升围栏值以将命令标记到该围栏点。
mCurrentFence++;
//将指令添加到命令队列以设置新的防护点。 因为我们
//在GPU时间轴上,直到GPU完成后才会设置新的围栏点
//处理此Signal()之前的所有命令。
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(),mCurrentFence));
//等待直到GPU完成命令为止。
if (mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle=CreateEventEx(nullptr,false,false,EVENT_ALL_ACCESS);
//GPU击中当前围墙时触发事件。
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence,eventHandle));
//等到GPU击中当前的fence事件后再触发。
WaitForSingleObject(eventHandle,INFINITE);
CloseHandle(eventHandle);
}
}
void DXApp::CreateSwapChain()
{
mSwapChain.Reset();
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 = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// Note: Swap chain uses queue to perform flush.
ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
}
void DXApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mDirectCmdListAlloc.Get(), nullptr, IID_PPV_ARGS(mCommandList.ReleaseAndGetAddressOf())));
//从关闭状态开始。 这是因为我们第一次提到
//在命令列表中,我们将对其进行重置,并且需要先关闭它
//调用Reset。
mCommandList->Close();
}
ID3D12Resource* DXApp::CurrentBackBuffer() const
{
return mSwapChainBuffer[mCurrBackBuffer].Get();
}
D3D12_CPU_DESCRIPTOR_HANDLE DXApp::CurrentBackBufferView() const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
mCurrBackBuffer,
mRtvDescriptorSize);
}
D3D12_CPU_DESCRIPTOR_HANDLE DXApp::DepthStencilView() const
{
return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}
void DXApp::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]);
}
}
void DXApp::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, mBackBufferFormat);
ReleaseCom(output);
++i;
}
}
void DXApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
UINT count = 0;
UINT flags = 0;
// Call with nullptr to get list count.
output->GetDisplayModeList(format, flags, &count, nullptr);
std::vector<DXGI_MODE_DESC> modeList(count);
output->GetDisplayModeList(format, flags, &count, &modeList[0]);
for (auto& x : modeList)
{
UINT n = x.RefreshRate.Numerator;
UINT d = x.RefreshRate.Denominator;
std::wstring text =
L"Width = " + std::to_wstring(x.Width) + L" " +
L"Height = " + std::to_wstring(x.Height) + L" " +
L"Refresh = " + std::to_wstring(n) + L"/" + std::to_wstring(d) +
L"\n";
::OutputDebugString(text.c_str());
}
}
DXApp* DXApp::GetApp()
{
return mApp;
}
HINSTANCE DXApp::AppInst() const
{
return mhAppInst;
}
HWND DXApp::MainWnd() const
{
return mhMainWnd;
}
float DXApp::AspectRatio() const
{
return static_cast<float>(mClientWidth) / mClientHeight;
}
bool DXApp::Get4xMsaaState() const
{
return m4xMsaaState;
}
void DXApp::Set4xMsaaState(bool value)
{
if (m4xMsaaState!=value)
{
m4xMsaaState = value;
//重置4xmasaa需要重新创建交换链和刷新尺寸
CreateSwapChain();
OnResize();
}
}
int DXApp::Run()
{
MSG msg = { 0 };
mTimer.Reset();
while (msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();
if (!mAppPaused)
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}
void DXApp::CalculateFrameStats()
{
// Code computes the average frames per second, and also the
// average time it takes to render one frame. These stats
// are appended to the window caption bar.
static int frameCnt = 0;
static float timeElapsed = 0.0f;
frameCnt++;
// Compute averages over one second period.
if ((mTimer.TotalTime() - timeElapsed) >= 1.0f)
{
float fps = (float)frameCnt; // fps = frameCnt / 1
float mspf = 1000.0f / fps;
wstring fpsStr = to_wstring(fps);
wstring mspfStr = to_wstring(mspf);
wstring windowText = mMainWndCaption +
L" fps: " + fpsStr +
L" mspf: " + mspfStr;
SetWindowText(mhMainWnd, L"window text");
// Reset for next average.
frameCnt = 0;
timeElapsed += 1.0f;
}
}
LRESULT DXApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
//激活或停用窗口时发送WM_ACTIVATE。
//当停用窗口时我们暂停游戏
//当它变为活动状态时取消暂停
case WM_ACTIVATE:
if (LOWORD(wParam)==WA_INACTIVE)
{
mAppPaused = true;
mTimer.Stop();
}
else
{
mAppPaused = false;
mTimer.Start();
}
return 0;
// 当用户调整窗口大小时,发送WM_SIZE。
case WM_SIZE:
// 保存新的客户区域尺寸。
mClientWidth = LOWORD(lParam);
mClientHeight = HIWORD(lParam);
if (md3dDevice)
{
if (wParam == SIZE_MINIMIZED)
{
mAppPaused = true;
mMinimized = true;
mMaximized = false;
}
else if (wParam == SIZE_MAXIMIZED)
{
mAppPaused = false;
mMinimized = false;
mMaximized = true;
OnResize();
}
else if (wParam == SIZE_RESTORED)
{
// Restoring from minimized state?
if (mMinimized)
{
mAppPaused = false;
mMinimized = false;
OnResize();
}
// Restoring from maximized state?
else if (mMaximized)
{
mAppPaused = false;
mMaximized = false;
OnResize();
}
else if (mResizing)
{
//如果用户拖动调整大小栏,我们不会调整大小
//这里的缓冲区,因为随着用户的不断前进
//拖动调整大小条,WM_SIZE消息流是
//发送到窗口,它将毫无意义(而且很慢)
//为从拖动中收到的每个WM_SIZE消息调整大小
//调整尺寸栏。 因此,我们在用户
//完成调整窗口大小并释放大小调整条
//发送WM_EXITSIZEMOVE消息。
}
else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
{
OnResize();
}
}
}
return 0;
// 当用户抓住调整大小条时,将发送WM_EXITSIZEMOVE。
case WM_ENTERSIZEMOVE:
mAppPaused = true;
mResizing = true;
mTimer.Stop();
return 0;
//当用户释放大小调整条时,发送WM_EXITSIZEMOVE。
//在这里,我们根据新窗口的尺寸重置所有内容。
case WM_EXITSIZEMOVE:
mAppPaused = false;
mResizing = false;
mTimer.Start();
OnResize();
return 0;
//销毁窗口时发送WM_DESTROY。
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// 当菜单处于活动状态并且用户按下时,将发送WM_MENUCHAR消息与任何助记键或加速键都不对应的键。
case WM_MENUCHAR:
// 进入时不要发出哔声。
return MAKELRESULT(0, MNC_CLOSE);
//捕获此消息,以防止窗口变得太小。
case WM_GETMINMAXINFO:
((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
return 0;
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_MOUSEMOVE:
OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_KEYUP:
if (wParam == VK_ESCAPE)
{
PostQuitMessage(0);
}
else if ((int)wParam == VK_F2)
Set4xMsaaState(!m4xMsaaState);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}