DirectX12_Windows_GameDevelop_3:Direct3D的初始化
引言
- 查看龙书时发现,第四章介绍预备知识的代码不太利于学习。因为它不像是LearnOpenGL那样从头开始一步一步教你敲代码,导致你没有一种整体感。
- 如果你把它当作某一块的代码进行学习,你跟着敲会发现,总有几个变量是没有定义的。这是因为书上的代码都是把框架里的某一部分粘过来,缺少上文中对变量的定义,也根本不利于学习。
- 学习图形学API就是为了使用GPU进行图形运算,说白了我们学习的DirectX就是一个工具,因此熟练掌握工具、能使用工具生产作品才是最重要的。因此不妨从4.3开始学习,学到那块不会再查了解前面的预备知识就会好很多。
- 由于现在学习的代码都是框架中的一部分,因此我的学习方法是:
1. 看书学习理论,并查看书中代码。
2. 在VS中Ctrl+F搜索书中代码,对照搜索到的代码框架跟着敲。如果遇到哪个变量是未定义的,直接Ctrl+鼠标左键(点击未定义的变量),跳转到这个变量的定义后,复制定义到我的代码里。
3. 如果遇到书中未详细介绍的内容,直接在MSDN查询相关信息。 - 下图展示了我从4.3章节开始,根据龙书源代码框架,学习书本示例代码的过程:
- 本文包含的核心英文单词和释义如下:
英文单词 | 中文含义 |
---|---|
Create | 创建 |
Fence | 围栏 |
Factory | 工厂 |
Controller | 控制器 |
Failed | 失败 |
FEATURE | 特性 |
Level | 级别 |
Adapter | 适配器 |
Enable | 启用 |
Enum | 枚举 |
Descriptor | 描述符 |
Handle | 句柄 |
Increment | 增量 |
HEAP | 堆 |
TYPE LESS | 无类型 |
Support | 支持 |
Quality | 质量 |
Sample Count | 采样数 |
NORM | 规范 |
MULTI | 多 |
Flags | 标志 |
command | 命令 |
Queue | 队列 |
DESC | 描述 |
Allocator | 分配器 |
DIRECT | 直接的 |
Address | 住址 |
Refresh Rate | 刷新率 |
numerator | 分子 |
Denominator | 分母 |
Scanline Ordering | 扫描线排序 |
MODE | 模式 |
SCANLINE | 扫描线 |
UNSPECIFIED | 未指明 |
Scaling | 缩放 |
RENDER TARGET OUTPUT | 渲染目标输出 |
一、初始化Direct3D
- 初始化Direct3D的9个步骤如下:
步骤序号 | 步骤内容 |
---|---|
1 | 用 D3D12CreateDevice 函数创建 ID3D12Device 接口实例 |
2 | 创建一个 ID3D12Fence 对象,并查询描述符的大小 |
3 | 检测用户设备对 4X MSAA 质量级别的支持 |
4 | 依次创建命令队列、命令列表分配器和主命令列表 |
5 | 描述并创建交换链 |
6 | 创建应用程序所需的描述符堆 |
7 | 调整后台缓冲区的大小,并为它创建渲染目标视图 |
8 | 创建深度/模板缓冲区及与之关联的深度/模板视图 |
9 | 设置视口和裁剪矩形 |
- 书中的代码没有上下文和完整框架,不妨跟着我一起学!
- 先看书中4.3章对每一个步骤的描述,再查看我提供的完整代码,二者对照岂不美哉!
- 本文第一章分为九个小节,对应初始化Direct3d的九个步骤,每个小节第一部分提供了完整可运行代码,第二部分提供代码涉及的知识点。
(1)创建设备
1.1 完整示例
- 要初始Direct3D,必须先创建Direct3D设备(ID3D12Device)。
- Direce3D设备代表着一个显示适配器,显示适配器一般指3D图形硬件即显卡。但是显示适配器也可以用软件来代替,通过模拟硬件显卡的计算处理过程,软件也可以作为适配器(如WARP适配器)。
- Direct3D设备既可以检测系统环境对功能的支持情况,又能创建所有其他的Direct3D接口对象(如资源、视图和命令列表)。
- 创建Direct3D设备使用函数D3D12CreateDevice。
- 话不多说,直接上代码:
#include<windows.h> // windows API编程所需
#include<wrl.h> // 提供了ComPtr类,它是COM对象的智能指针,使我们无需手动Release
#include<d3d12.h> // Direct3D12头文件,ID3D12开头类型始于此
#include<dxgi1_4.h> // DirectX图形基础设施头文件,IDXGI开头类型始于此
#include<string> // 提供wsring类,在Windows平台上应该使用wstring和wchar_tusing namespace Microsoft::WRL; // 方便使用Microsoft::WRL::ComPtr类 // AnsiToWString函数(将字符串映射到 UTF-16 (宽字符) 字符串)
inline std::wstring AnsiToWString(const std::string& str)
{WCHAR buffer[512];MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);return std::wstring(buffer);
}// 定义异常类
class DxException
{
public:DxException() = default;// 显示:异常函数的返回值、函数名、代码所处文件名,所处代码行数DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);std::wstring ToString()const;HRESULT ErrorCode = S_OK;std::wstring FunctionName;std::wstring Filename;int LineNumber = -1;
};// 如果发生异常,抛出一个异常实例
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \HRESULT hr__ = (x); \std::wstring wfn = AnsiToWString(__FILE__); \if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
}
#endif// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;// 初始化Direct3D
bool InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG) // 如果在Debug模式,启用D3D12的调试层,// 启用调试后,D3D会在错误发生时向VC++的输出窗口发送调试信息{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();}
#endif// 创建可用于生成其他 DXGI 对象的 DXGI 1.0 FactoryThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));// 尝试创建一个D3D硬件适配器// D3D12CreateDevice参数为:(适配器指针,应用程序所需最低功能级别,// 所建ID3D12Device接口的COM ID,返回所创建的D3D12设备HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice));// 适配器指针传入空代表使用主显示适配器,// 可以分析出需要COM指针即riid的地方,通过使用IID_PPV_ARGS即可// 如果创建失败,应用程序回退至WARP软件适配器if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter> pWarpAdapter;ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));// 不同windows版本的WARP最高支持的功能级别也不同// 再次创建D3D设备时,仅适配器指针参数不同,传入WARP适配器指针ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(),D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice)));}
}int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd)
{// 在主函数中调用Direct3D的初始化函数InitDirect3D();
}
- 代码如上,大致过一遍有个记忆就行。如果对其中哪些地方有疑问或者好奇,可以在MSDN上搜索相关类型或函数查看相关信息。
1.2 相关知识
- 组件对象模型(Component Object Model,COM)是一种令DirectX不受编程语言束缚,并且使之向后兼容的技术。我们通常将COM对象视为一种接口,但由于我们的编程目的,我们可以把它当作一个C++类来使用。当我们使用C++编写DirectX程序时,COM帮我们隐藏了大量的底层细节。我们只需要知道,要获取指向某COM接口的指针,需借助特定函数或另一COM接口的方法。
- Windows运行时库(Windows Runtime Library,WRL)为COM对象提供了Microsoft::WRL::ComPtr类,它位于<wrl.h>头文件中,它是COM对象的智能指针。当一个COM对象超出作用域范围时,智能指针会调用相应COM对象的Release方法,省去了我们手动调用的麻烦。
- 书中的COM接口、对象和指针,让我晕头转向,因为我并没有接触过COM技术。分析一下,上文示例代码中有:
// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;
- 无论是IDXGIFactory4还是ID3D12Device,这两种Direct3D中的对象都是通过COM指针来表示的。因此我们现在可以知道的是:
使用C++开发Direct3D应用程序时,每个Direct3D对象都是一种COM对象或者说COM接口,我们要记录对象就要使用COM指针,我们要获取这种对象就要使用特定函数或另一COM对象的方法。 - 不必迷茫,只需要知道Direct3D对象的记录和获取方法如下即可:
// Direct3D对象的记录方法
ComPtr<Direct3D对象类型> 对象名。// Direct3d对象的获取方法
对象名 = 特定函数();
对象名 = 其他Direct3D对象.函数();
- ComPtr类的三个常用函数如下:
函数名 | 描述 |
---|---|
Get | 返回指向此底层COM对象的指针 |
GetAddressOf | 返回指向此底层COM对象的指针的地址 |
Rest | 将ComPtr实例设置为nullptr释放与之相关的所有引用 |
- 注意:
Rest函数的作用和将ComPtr对象赋值为nullptr的效果相同。
COM对象都以大写字母 “I” 开头,例如表示设备的COM对象为ID3D12Device。
COM对象、COM接口、COM实例,都是一个意思。 - 读到这里就清楚了吧,为了使DirectX不受语言束缚,DirectX中的对象都被定义为一种以 “I” 开头的COM对象。而使用COM对象最方便的方法就是使用COM指针即ComPtr类,因此我们使用ComPtr类记录每个DirectX对象即可。
- 如果读者对Windows平台为什么要使用wstring等问题感兴趣,可以查找MSDN或其他搜索。如果对IID_PPV_ARGS宏感兴趣,可以查看书籍4.2.1即95页中间对其描述:这个宏会展开为两项,第一项根据__uuidof获取了COM对象的ID(全局唯一标识符,GUID)。IID_PPV_ARGS辅助函数本质是把指针强制转换为void类型。
#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
(2)创建围栏并获取描述符的大小
2.1 完整示例
- CPU和GPU的同步需要使用到围栏,第一步中我们创建好设备,第二步就可以创建围栏了。
- 另外,如果使用描述符进行工作,需要获取描述符的大小。描述符在不同GPU平台上的大小不同,因此我们需要将获取的描述符大小信息缓存起来,方便需要时直接引用。
- 话不多说,直接上代码:
// ID3D12Fence: 表示围栏ComPtr<ID3D12Fence> mFence;// 创建围栏ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));// RTV描述符大小,RTV描述符: 渲染目标视图资源UINT mRtvDescriptorSize = 0;// DSV描述符大小,DSV描述符: 深度/模板视图资源UINT mDsvDescriptorSize = 0;// CbvSrvUav描述符大小,CBV描述符: 常量缓冲区视图资源...UINT mCbvSrvUavDescriptorSize = 0;// 获取描述符大小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);
- 上述代码直接放在第一步 InitDirect3D 函数末尾即可,需要注意的是mFence、mRtvDescriptorSize和mDsvDescriptorSize等变量应该定义为全局变量,上文为易于理解才写在函数内部。
- 可以看到新出现的四个变量都是通过 md3dDevice 对象获取到的,印证了:
1. “Direct3D12 设备对象能创建所有其他的Direct3D接口对象”。
2. “COM对象需要使用特定方法或通过其他COM对象的方法获取”。
2.2 相关知识
- 资源与描述符,详见4.1.6节。在渲染过程中,GPU需要对资源进行读和写。但是GPU和资源并不是直接绑定的,而是通过一个媒婆 “描述符” ,这个中间人进行绑定的。
- 描述符是一种对送往GPU的资源进行描述的轻量级结构,它是一个中间层。当GPU需要对资源进行读或写时,GPU就会问媒婆:“资源在哪里?我应该按照哪种数据格式进行读写?”。
- 可见描述符的作用有两点:
1. 指定资源数据。
2. 为GPU解释资源信息。 - 创建资源时可用无类型格式,如DXGI_FORMAT_R8G8B8A8_TYPELESS类型,我们知道它是4个分量组成,每个分量占8个位,但是我们不知道每个分量的8个位应该解析为整数、浮点数还是无符号整数?
- 如果某个资源在创建时采用了无类型格式,那么在为它创建描述符时必须指明其具体类型。
- 注意:视图和描述符的含义是等价的。
- 每个描述符都有一种具体类型,此类型指明了资源的具体作用。常用的描述符如下:
描述符 | 含义 |
---|---|
CBV | 常量缓冲区视图 |
SRV | 着色器资源视图 |
UAV | 无序访问视图 |
sampler | 采样器资源描述符 |
RTV | 渲染目标视图资源 |
DSV | 深度/模板视图资源 |
- 描述符堆中存有一系列描述符,本质上是存放用户程序中某特定类型描述符的一块内存。我们需要为每一种类型的描述符创建出单独的描述符,当然同一种描述符也可以创建多个描述符堆。
- 我们可以用不同的描述符来描述同一个资源,达到以不同的数据格式或内容部分去读写资源的目的。
- 由于创建描述符的过程中需要执行一些类型的检测和验证工作,索引最好不要在运行时才创建描述符,创建描述符的最佳时机为初始化期间。
- 当然有时确实需要使用无类型资源所带来的灵活性,此时在一定限度内,可以考虑在运行时创建描述符。
(3)检测对4X MSAA 质量级别的支持
3.1 完整示例
- 本书中我们要使用4X MSAA,之所以选择4X,是因为借助此采样数量就可以获取开销不高但性能非凡的效果。而使用4X MSAA之前,我们要先检查设备是否支持4X MSAA 质量级别的图像。
- 话不多说,直接上代码:
#include<windows.h> // windows API编程所需
#include<wrl.h> // 提供了ComPtr类,它是COM对象的智能指针,使我们无需手动Release
#include<d3d12.h> // Direct3D12头文件,ID3D12开头类型始于此
#include<dxgi1_4.h> // DirectX图形基础设施头文件,IDXGI开头类型始于此
#include<string> // 提供wsring类,在Windows平台上应该使用wstring和wchar_t
#include<assert.h>using namespace Microsoft::WRL; // 方便使用Microsoft::WRL::ComPtr类 // AnsiToWString函数(将字符串映射到 UTF-16 (宽字符) 字符串)
inline std::wstring AnsiToWString(const std::string& str)
{WCHAR buffer[512];MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);return std::wstring(buffer);
}// 定义异常类
class DxException
{
public:DxException() = default;// 显示:异常函数的返回值、函数名、代码所处文件名,所处代码行数DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);std::wstring ToString()const;HRESULT ErrorCode = S_OK;std::wstring FunctionName;std::wstring Filename;int LineNumber = -1;
};// 如果发生异常,抛出一个异常实例
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \HRESULT hr__ = (x); \std::wstring wfn = AnsiToWString(__FILE__); \if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
}
#endif// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;// 初始化Direct3D
bool InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG) // 如果在Debug模式,启用D3D12的调试层,// 启用调试后,D3D会在错误发生时向VC++的输出窗口发送调试信息{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();}
#endif// 创建可用于生成其他 DXGI 对象的 DXGI 1.0 FactoryThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));// 第一步:// 创建一个D3D设备// D3D12CreateDevice参数为:(适配器指针,应用程序所需最低功能级别,// 所建ID3D12Device接口的COM ID,返回所创建的D3D12设备HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice));// 适配器指针传入空代表使用主显示适配器,// 可以分析出需要COM指针即riid的地方,通过使用IID_PPV_ARGS即可// 如果创建失败,应用程序回退至WARP软件适配器if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter> pWarpAdapter;ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));// 不同windows版本的WARP最高支持的功能级别也不同// 再次创建D3D设备时,仅适配器指针参数不同,传入WARP适配器指针ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(),D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice)));}// 第二步:// ID3D12Fence: 表示围栏ComPtr<ID3D12Fence> mFence;// 创建围栏ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));// RTV描述符大小,RTV描述符: 渲染目标视图资源UINT mRtvDescriptorSize = 0;// DSV描述符大小,DSV描述符: 深度/模板视图资源UINT mDsvDescriptorSize = 0;// CbvSrvUav描述符大小,CBV描述符: 常量缓冲区视图资源...UINT mCbvSrvUavDescriptorSize = 0;// 获取描述符大小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);// 第三步:// DXGI_FORMAT: 资源数据的格式,一种枚举类型 DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;// 检测对4X MASS质量级别的支持D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;msQualityLevels.Format = mBackBufferFormat; // 纹理格式msQualityLevels.SampleCount = 4; // 采样次数msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE; // 默认选项msQualityLevels.NumQualityLevels = 0; // 质量级别// 使用ID3D12Device::CheckFeatureSupport函数,查询我们设置的这种图像质量的级别ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));// 使用无符号整数存储:我们查询的图像质量所对应的质量级别(不为零则说明支持)UINT m4xMsaaQuality = 0;// 我们查询的采样数为4,因此返回的质量级别,即为达到4X MSAA的质量级别m4xMsaaQuality = msQualityLevels.NumQualityLevels;// 只要返回的质量级别不为零,则表示支持此种图像质量,在该处即支持4X MSAAassert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");
}int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd)
{InitDirect3D();
}
- 在此给出完整代码,若读者对第二步的完整代码不清楚,也可以在此查看。
- 可以看到我们先定义了一种资源类型的格式,那就是要查询的纹理格式。你可以把代码复制到你的项目里运行试试,当然你要记得使用Windows API开发项目,如果只会创建控制台项目,你可以看看我之前文章中的第六步。
- 详细的讲解都在代码里了,我就不多说了。我的显卡不是很好,但运行后依旧不会报错,你可以试试运行,如果报错就说明你的显卡不支持这种4X MSAA 的图像质量级别了。
- 你可以将纹理资源的格式改为:DXGI_FORMAT_R16G16B16A16_UNORM,这样的纹理显然更精细点,然后再运行试试。
- 多次运行,你发现都不会报错,你觉得要么是程序错了要么是你的显卡太棒了!那不妨试试修改采样数量,即msQualityLevels.SampleCount,将它改为8、16和32呢?
3.2 相关知识
- 多重采样技术的原理位于4.1.7小节,在书籍85页,我就不做过多阐述了。需要注意的是ID3DDevice->CheckFeatureSupport方法的第二个参数兼具输入和输出的属性。
- 如果不希望使用多重采样,可以将采用数量设置为1,并将质量级别设为0。
- 在创建交换链缓冲区和深度缓冲区时都需要填写DXGI_SAMPLE_DESC结构体。当创建后台缓冲区和深度缓冲区时,多重采样的有关设置必须相同。
- 功能支持的检测位于4.1.11小节。函数ID3D12Device::CheckFeatureSupport可以对许多功能进行检测,其第一个参数的类型是一个枚举类:D3D12_FEATURE,第二个参数是枚举类对应的数据结构指针,第三个参数是传入的数据结构变量所占字节数。
- 使用ID3D12Device::CheckFeatureSupport检测系统支持Direct3D最高版本的代码为:
#include<windows.h> // windows API编程所需
#include<wrl.h> // 提供了ComPtr类,它是COM对象的智能指针,使我们无需手动Release
#include<d3d12.h> // Direct3D12头文件,ID3D12开头类型始于此
#include<dxgi1_4.h> // DirectX图形基础设施头文件,IDXGI开头类型始于此
#include<string> // 提供wsring类,在Windows平台上应该使用wstring和wchar_t
#include<assert.h>using namespace Microsoft::WRL; // 方便使用Microsoft::WRL::ComPtr类 // AnsiToWString函数(将字符串映射到 UTF-16 (宽字符) 字符串)
inline std::wstring AnsiToWString(const std::string& str)
{WCHAR buffer[512];MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);return std::wstring(buffer);
}// 定义异常类
class DxException
{
public:DxException() = default;// 显示:异常函数的返回值、函数名、代码所处文件名,所处代码行数DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);std::wstring ToString()const;HRESULT ErrorCode = S_OK;std::wstring FunctionName;std::wstring Filename;int LineNumber = -1;
};// 如果发生异常,抛出一个异常实例
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \HRESULT hr__ = (x); \std::wstring wfn = AnsiToWString(__FILE__); \if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
}
#endif// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;// 初始化Direct3D
bool InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG) // 如果在Debug模式,启用D3D12的调试层,// 启用调试后,D3D会在错误发生时向VC++的输出窗口发送调试信息{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();}
#endif// 创建可用于生成其他 DXGI 对象的 DXGI 1.0 FactoryThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));// 创建一个D3D设备// D3D12CreateDevice参数为:(适配器指针,应用程序所需最低功能级别,// 所建ID3D12Device接口的COM ID,返回所创建的D3D12设备HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice));// 适配器指针传入空代表使用主显示适配器,// 可以分析出需要COM指针即riid的地方,通过使用IID_PPV_ARGS即可// 如果创建失败,应用程序回退至WARP软件适配器if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter> pWarpAdapter;ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));// 不同windows版本的WARP最高支持的功能级别也不同// 再次创建D3D设备时,仅适配器指针参数不同,传入WARP适配器指针ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(),D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice)));}// ID3D12Fence: 表示围栏ComPtr<ID3D12Fence> mFence;// 创建围栏ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));// RTV描述符大小,RTV描述符: 渲染目标视图资源UINT mRtvDescriptorSize = 0;// DSV描述符大小,DSV描述符: 深度/模板视图资源UINT mDsvDescriptorSize = 0;// CbvSrvUav描述符大小,CBV描述符: 常量缓冲区视图资源...UINT mCbvSrvUavDescriptorSize = 0;// 获取描述符大小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);// DXGI_FORMAT: 资源数据的格式,一种枚举类型 DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;// 检测对4X MASS质量级别的支持D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;msQualityLevels.Format = mBackBufferFormat; // 纹理格式msQualityLevels.SampleCount = 4; // 采样次数msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE; // 默认选项msQualityLevels.NumQualityLevels = 0; // 质量级别// 使用ID3D12Device::CheckFeatureSupport函数,查询我们设置的这种图像质量的级别ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));// 使用无符号整数存储:我们查询的图像质量所对应的质量级别(不为零则说明支持)UINT m4xMsaaQuality = 0;// 我们查询的采样数为4,因此返回的质量级别,即为达到4X MSAA的质量级别m4xMsaaQuality = msQualityLevels.NumQualityLevels;// 只要返回的质量级别不为零,则表示支持此种图像质量,在该处即支持4X MSAAassert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");// 检测支持的Direct3D最高版本// 需要检测的版本D3D_FEATURE_LEVEL feature[3] ={D3D_FEATURE_LEVEL_12_0,D3D_FEATURE_LEVEL_12_1,D3D_FEATURE_LEVEL_12_2,};// 枚举类型对应的数据结构D3D12_FEATURE_DATA_FEATURE_LEVELS featureLevelsInfo;featureLevelsInfo.NumFeatureLevels = 3; // 检测三种版本featureLevelsInfo.pFeatureLevelsRequested = feature; // 检测的版本// 执行检测函数md3dDevice->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS,&featureLevelsInfo,sizeof(featureLevelsInfo));// 返回支持的最高版本(可以打个断点查看一下)featureLevelsInfo.MaxSupportedFeatureLevel;
}int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd)
{InitDirect3D();
}
- 我最高支持的版本是:D3D_FEATURE_LEVEL_12_1。真是太捞了,等工作有钱了去买一台intel i9 + 4090,那时候估计就不贵了,释放性能!
(4)创建命令队列和列表
4.1 完整示例
- 我们需要使用命令队列来存储GPU需要执行的命令,用命令列表来存储CPU想要提交的命令。过程非常简单,让我们直接来做吧!
- 代码如下:
#include<windows.h> // windows API编程所需
#include<wrl.h> // 提供了ComPtr类,它是COM对象的智能指针,使我们无需手动Release
#include<d3d12.h> // Direct3D12头文件,ID3D12开头类型始于此
#include<dxgi1_4.h> // DirectX图形基础设施头文件,IDXGI开头类型始于此
#include<string> // 提供wsring类,在Windows平台上应该使用wstring和wchar_t
#include<assert.h>using namespace Microsoft::WRL; // 方便使用Microsoft::WRL::ComPtr类 // AnsiToWString函数(将字符串映射到 UTF-16 (宽字符) 字符串)
inline std::wstring AnsiToWString(const std::string& str)
{WCHAR buffer[512];MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);return std::wstring(buffer);
}// 定义异常类
class DxException
{
public:DxException() = default;// 显示:异常函数的返回值、函数名、代码所处文件名,所处代码行数DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);std::wstring ToString()const;HRESULT ErrorCode = S_OK;std::wstring FunctionName;std::wstring Filename;int LineNumber = -1;
};// 如果发生异常,抛出一个异常实例
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \HRESULT hr__ = (x); \std::wstring wfn = AnsiToWString(__FILE__); \if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
}
#endif// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;// ID3D12Fence: 表示围栏
ComPtr<ID3D12Fence> mFence;// RTV描述符大小,RTV描述符: 渲染目标视图资源
UINT mRtvDescriptorSize = 0;
// DSV描述符大小,DSV描述符: 深度/模板视图资源
UINT mDsvDescriptorSize = 0;
// CbvSrvUav描述符大小,CBV描述符: 常量缓冲区视图资源...
UINT mCbvSrvUavDescriptorSize = 0;// DXGI_FORMAT: 资源数据的格式,一种枚举类型
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;// 使用无符号整数存储:我们查询的图像质量所对应的质量级别(不为零则说明支持)
UINT m4xMsaaQuality = 0;// 第四步: 创建命令队列和命令列表
void CreateCommandObjects()
{// 声明一个命令队列ComPtr<ID3D12CommandQueue> mCommandQueue;// 调用ID3D12Device::CreateCommandQueue方法创建队列前,// 需要填写D3D12_COMMAND_QUEUE_DESC结构体来描述队列D3D12_COMMAND_QUEUE_DESC queueDesc = {};// 定义队列中的命令类型queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;// 设置其他选项为空queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;// 调用ID3D12Device::CreateCommandQueue方法创建命令队列ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));// 声明ID3D12CommandAllocator命令内存管理对象ComPtr<ID3D12CommandAllocator> mCommandAllocator;// 记录在命令列表内的命令,实际上是存储在与之关联的命令分配器上// 通过ID3D12Device创建命令分配器ID3D12CommandAllocatorThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, // 与此命令分配器关联的命令列表类型IID_PPV_ARGS(mCommandAllocator.GetAddressOf()) ));// 通过ID3D12Device创建命令列表ID3D12GraphicsCommandListComPtr<ID3D12GraphicsCommandList> mCommandList;ThrowIfFailed(md3dDevice->CreateCommandList(0, // 指定与所建命令列表相关联的物理GPU,对于仅有一个GPU的系统而言设为0D3D12_COMMAND_LIST_TYPE_DIRECT, // 命令列表的类型mCommandAllocator.Get(), // 关联的命令分配器nullptr, // 打包和初始化无绘制命令时传nullptrIID_PPV_ARGS(mCommandList.GetAddressOf())// 输出指向所建列表的指针));// 在关闭状态下启动// 这是因为当我们第一次引用命令列表时,我们会重置它,并且在调用Reset之前需要关闭它。mCommandList->Close();
}// 初始化Direct3D
bool InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG) // 如果在Debug模式,启用D3D12的调试层,// 启用调试后,D3D会在错误发生时向VC++的输出窗口发送调试信息{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();}
#endif/*第一步:创建Direct3D设备*/ // 创建可用于生成其他 DXGI 对象的 DXGI 1.0 FactoryThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));// 创建一个D3D设备// D3D12CreateDevice参数为:(适配器指针,应用程序所需最低功能级别,// 所建ID3D12Device接口的COM ID,返回所创建的D3D12设备HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice));// 适配器指针传入空代表使用主显示适配器,// 可以分析出需要COM指针即riid的地方,通过使用IID_PPV_ARGS即可// 如果创建失败,应用程序回退至WARP软件适配器if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter> pWarpAdapter;ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));// 不同windows版本的WARP最高支持的功能级别也不同// 再次创建D3D设备时,仅适配器指针参数不同,传入WARP适配器指针ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(),D3D_FEATURE_LEVEL_12_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的支持*/// 检测对4X 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; // 质量级别// 使用ID3D12Device::CheckFeatureSupport函数,查询我们设置的这种图像质量的级别ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));// 我们查询的采样数为4,因此返回的质量级别,即为达到4X MSAA的质量级别m4xMsaaQuality = msQualityLevels.NumQualityLevels;// 只要返回的质量级别不为零,则表示支持此种图像质量,在该处即支持4X MSAAassert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");CreateCommandObjects();
}int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd)
{InitDirect3D();
}
- 可以看到在函数CreateCommandObjects()中我们完成了命令队列和命令列表的创建,当然为了易于理解我将命令队列、命令列表和命令分配器声明为了局部变量,你应该把它们放到函数外面作为全局变量,如下所示:
// 声明一个命令队列
ComPtr<ID3D12CommandQueue> mCommandQueue;// 声明ID3D12CommandAllocator命令内存管理对象
ComPtr<ID3D12CommandAllocator> mCommandAllocator;// 通过ID3D12Device创建命令列表ID3D12GraphicsCommandList
ComPtr<ID3D12GraphicsCommandList> mCommandList;void CreateCommandObjects()
{// 调用ID3D12Device::CreateCommandQueue方法创建队列前,// 需要填写D3D12_COMMAND_QUEUE_DESC结构体来描述队列D3D12_COMMAND_QUEUE_DESC queueDesc = {};// 定义队列中的命令类型queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;// 设置其他选项为空queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;// 调用ID3D12Device::CreateCommandQueue方法创建命令队列ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));// 记录在命令列表内的命令,实际上是存储在与之关联的命令分配器上// 通过ID3D12Device创建命令分配器ID3D12CommandAllocatorThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, // 与此命令分配器关联的命令列表类型IID_PPV_ARGS(mCommandAllocator.GetAddressOf()) ));ThrowIfFailed(md3dDevice->CreateCommandList(0, // 指定与所建命令列表相关联的物理GPU,对于仅有一个GPU的系统而言设为0D3D12_COMMAND_LIST_TYPE_DIRECT, // 命令列表的类型mCommandAllocator.Get(), // 关联的命令分配器nullptr, // 打包和初始化无绘制命令时传nullptrIID_PPV_ARGS(mCommandList.GetAddressOf())// 输出指向所建列表的指针));// 在关闭状态下启动// 这是因为当我们第一次引用命令列表时,我们会重置它,并且在调用Reset之前需要关闭它。mCommandList->Close();
}
- 现在你就不会感觉好像什么都没有得到了吧!从这里我们也可以看出初始化Direct3D工作的一大重点就是:声明一系列核心的Direct3D变量,并且在初始化时获取它们!
4.2 相关知识
- 在进行图形学编程的时候,有两种处理器在参与工作:CPU和GPU。两者并行运行,但有时也需要同步。为了获得最佳性能,理想的情况下是不进行同步,但许多时候同步是必须要进行的。
- 每个GPU都至少维护着一个命令队列(本质上是环形缓冲区,即ring buffer)。使用Direct3D API,CPU可利用命令列表将命令提交到这个队列中去。
- 当一系列命令被提交至命令队列之时,它们并不会被GPU立即执行。由于GPU可能正在处理先前插入命令队列,因此新到达的命令常常会先等待再执行。
- 如果命令队列全空,会浪费GPU大量的运算能力。如果命令队列全满,会导致CPU不可避免的等待,因此会浪费CPU大量的运算能力。对于游戏这样的高性能应用程序来说,应该充分利用硬件资源,保持CPU和GPU的同时忙碌。
- ExecuteCommandLists是一种常用的ID3D12CommandQueue接口(对象)方法,利用它可将命令列表里的命令添加到命令队列中。它包含两个参数,第一个参数是UINT型变量,代表命令列表中命令的数量,第二个参数是命令列表数组的首指针。GPU会从数组中的第一个命令开始顺序执行。
- ID3D12GraphicsCommandList接口封装了一系列图形渲染命令,它实际上继承于ID3D12CommandList接口。调用其方法即可向命令列表添加命令,但需要注意的是命令并不会立即执行,其方法仅仅是将命令加入了命令列表而已。调用ExecuteCommandLists方法才会将命名真正地送入命令队列,供GPU在合适的时机处理。
- 随着内容不断深入,我们将逐步掌握D3D12GraphicsCommandList所支持的各种命令。当命令被加入命令列表后,我们必须调用D3D12GraphicsCommandList::Close()方法来结束命令的记录。在调用ExecuteCommandLists方法将命令提交给命令列表之前,一定要先将其关闭,即调用Close方法。
- 我们可以通过ID3D12Device->GetNodeCount()方法查询系统中GPU适配器节点(物理GPU)的数量。
- 很明显每个命令列表对应一个命令分配器,那么一个命令分配器是否可以对应多个命令列表呢?答案是可以的,我们可以让一个命令分配器关联多个命令列表,但必须关闭同一命令分配器的其他命令列表,即任何时候只能有一个命令列表处于打开状态。这保证了命令列表中的所有命令都会按顺序连续地添加到命令分配器内,即只有当正在使用的命令列表使用完已关闭时,其他命令列表才可以使用。
- 注意,当创建或重置一个命令列表时,它会处于 “打开” 的状态,因此你如果为同一个命令分配器连续创建或重置两个命令列表时,就会得到报错信息。
- 在调用ExecuteCommandLists方法将命令列表中的命令提交给命令队列后,我们就可以通过D3D12GraphicsCommandList::Reset方法将命令列表恢复为刚创建时的初态。此方法功能类似于std::vector::clear方法,仅使得存储元素的数量归零,但是仍保持其当前的容量。借助此方法,我们就可以继续复用其底层内存,避免释放列表再新建的繁琐操作。
- 命令队列可能会引用命令分配器中的数据,但是重置命令列表却不会影响命令队列,这是因为相关的命令分配器仍在维护着其内存中被命令队列引用的一系列命令。因此当调用ExecuteCommandLists方法后就可以重置命名列表。但是注意,在没有确定GPU执行完命令分配器中的所有命令之前,千万不要重置命令分配器!
- 因此差不多会在每一帧重置命令列表,但命令分配器却不一定。
(5)描述并创建交换链
5.1 完整示例
#include<windows.h> // windows API编程所需
#include<wrl.h> // 提供了ComPtr类,它是COM对象的智能指针,使我们无需手动Release
#include<d3d12.h> // Direct3D12头文件,ID3D12开头类型始于此
#include<dxgi1_4.h> // DirectX图形基础设施头文件,IDXGI开头类型始于此
#include<string> // 提供wsring类,在Windows平台上应该使用wstring和wchar_t
#include<assert.h>using namespace Microsoft::WRL; // 方便使用Microsoft::WRL::ComPtr类 // AnsiToWString函数(将字符串映射到 UTF-16 (宽字符) 字符串)
inline std::wstring AnsiToWString(const std::string& str)
{WCHAR buffer[512];MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, buffer, 512);return std::wstring(buffer);
}// 定义异常类
class DxException
{
public:DxException() = default;// 显示:异常函数的返回值、函数名、代码所处文件名,所处代码行数DxException(HRESULT hr, const std::wstring& functionName, const std::wstring& filename, int lineNumber);std::wstring ToString()const;HRESULT ErrorCode = S_OK;std::wstring FunctionName;std::wstring Filename;int LineNumber = -1;
};// 如果发生异常,抛出一个异常实例
#ifndef ThrowIfFailed
#define ThrowIfFailed(x) \
{ \HRESULT hr__ = (x); \std::wstring wfn = AnsiToWString(__FILE__); \if(FAILED(hr__)) { throw DxException(hr__, L#x, wfn, __LINE__); } \
}
#endif// IDXGIFactory: DXGI中最关键的接口之一,可以枚举显示适配器
ComPtr<IDXGIFactory4> mdxgiFactory;// ID3D12Device: 代表一个显示适配器(显卡)
ComPtr<ID3D12Device> md3dDevice;// ID3D12Fence: 表示围栏
ComPtr<ID3D12Fence> mFence;// RTV描述符大小,RTV描述符: 渲染目标视图资源
UINT mRtvDescriptorSize = 0;
// DSV描述符大小,DSV描述符: 深度/模板视图资源
UINT mDsvDescriptorSize = 0;
// CbvSrvUav描述符大小,CBV描述符: 常量缓冲区视图资源...
UINT mCbvSrvUavDescriptorSize = 0;// DXGI_FORMAT: 资源数据的格式,一种枚举类型
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;// 使用无符号整数存储:我们查询的图像质量所对应的质量级别(不为零则说明支持)
UINT m4xMsaaQuality = 0;// 声明一个命令队列
ComPtr<ID3D12CommandQueue> mCommandQueue;// 声明ID3D12CommandAllocator命令内存管理对象
ComPtr<ID3D12CommandAllocator> mCommandAllocator;// 通过ID3D12Device创建命令列表ID3D12GraphicsCommandList
ComPtr<ID3D12GraphicsCommandList> mCommandList;// 定义缓冲区分辨率的高度和宽度
int mClientWidth = 800;
int mClientHeight = 600;// 定义交换链中的缓冲区数目,这里使用双缓冲
const int SwapChainBufferCount = 2;// 存储窗口句柄
HWND mhMainWnd = nullptr;// 声明交换链接口(对象)IDXGISwapChain
ComPtr<IDXGISwapChain> mSwapChain;/*第五步:描述并创建交换链
*/
void CreateSwapChain()
{// 释放我们将要重新创建的上一个交换链。mSwapChain.Reset();// 使用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 = mBackBufferFormat; // 设置缓冲区格式sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; // 设置扫描线顺序为未指定sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 设置缩放为未指定sd.SampleDesc.Count = 1; // 设置采样数目为1sd.SampleDesc.Quality = 0; // 设置质量级别为0sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;// 设置后台缓冲区为渲染目标输出sd.BufferCount = SwapChainBufferCount; // 设置交换链中的缓冲区数量sd.OutputWindow = mhMainWnd; // 设置渲染窗口的句柄sd.Windowed = true; // 设置为true程序将在窗口模式运行,否则为全屏sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;// 可选标志,设为此项表示程序切换为全屏时,选择最适合于当前应用程序窗口尺寸的显示模式// 若无该标志,则采用当前桌面的显示模式。// 使用IDXGIFactory::CreateSwapChain方法,创建交换链接口ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(),&sd,mSwapChain.GetAddressOf()));
}/*第四步:创建命令队列和命令列表
*/
void CreateCommandObjects()
{// 调用ID3D12Device::CreateCommandQueue方法创建队列前,// 需要填写D3D12_COMMAND_QUEUE_DESC结构体来描述队列D3D12_COMMAND_QUEUE_DESC queueDesc = {};// 定义队列中的命令类型queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;// 设置其他选项为空queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;// 调用ID3D12Device::CreateCommandQueue方法创建命令队列ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));// 记录在命令列表内的命令,实际上是存储在与之关联的命令分配器上// 通过ID3D12Device创建命令分配器ID3D12CommandAllocatorThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, // 与此命令分配器关联的命令列表类型IID_PPV_ARGS(mCommandAllocator.GetAddressOf()) ));ThrowIfFailed(md3dDevice->CreateCommandList(0, // 指定与所建命令列表相关联的物理GPU,对于仅有一个GPU的系统而言设为0D3D12_COMMAND_LIST_TYPE_DIRECT, // 命令列表的类型mCommandAllocator.Get(), // 关联的命令分配器nullptr, // 打包和初始化无绘制命令时传nullptrIID_PPV_ARGS(mCommandList.GetAddressOf())// 输出指向所建列表的指针));// 在关闭状态下启动// 这是因为当我们第一次引用命令列表时,我们会重置它,并且在调用Reset之前需要关闭它。mCommandList->Close();md3dDevice->GetNodeCount();
}// 初始化Direct3D
bool InitDirect3D()
{
#if defined(DEBUG) || defined(_DEBUG) // 如果在Debug模式,启用D3D12的调试层,// 启用调试后,D3D会在错误发生时向VC++的输出窗口发送调试信息{ComPtr<ID3D12Debug> debugController;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));debugController->EnableDebugLayer();}
#endif/*第一步:创建Direct3D设备*/ // 创建可用于生成其他 DXGI 对象的 DXGI 1.0 FactoryThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));// 创建一个D3D设备// D3D12CreateDevice参数为:(适配器指针,应用程序所需最低功能级别,// 所建ID3D12Device接口的COM ID,返回所创建的D3D12设备HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_12_0,IID_PPV_ARGS(&md3dDevice));// 适配器指针传入空代表使用主显示适配器,// 可以分析出需要COM指针即riid的地方,通过使用IID_PPV_ARGS即可// 如果创建失败,应用程序回退至WARP软件适配器if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter> pWarpAdapter;ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));// 不同windows版本的WARP最高支持的功能级别也不同// 再次创建D3D设备时,仅适配器指针参数不同,传入WARP适配器指针ThrowIfFailed(D3D12CreateDevice(pWarpAdapter.Get(),D3D_FEATURE_LEVEL_12_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的支持*/// 检测对4X 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; // 质量级别// 使用ID3D12Device::CheckFeatureSupport函数,查询我们设置的这种图像质量的级别ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msQualityLevels,sizeof(msQualityLevels)));// 我们查询的采样数为4,因此返回的质量级别,即为达到4X MSAA的质量级别m4xMsaaQuality = msQualityLevels.NumQualityLevels;// 只要返回的质量级别不为零,则表示支持此种图像质量,在该处即支持4X MSAAassert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");/*第四步:创建命令队列和命令列表*/CreateCommandObjects();/*第五步:描述并创建交换链*/CreateSwapChain();
}#include<Windows.h>#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define WINDOW_TITLE L"GameEngine"LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);int WINAPI WinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPSTR lpCmdLine,_In_ int nShowCmd)
{SetProcessDPIAware();int cx = GetSystemMetrics(SM_CXSCREEN);int cy = GetSystemMetrics(SM_CYMAXTRACK);WNDCLASSEX wndClass = { 0 };wndClass.cbSize = sizeof(WNDCLASSEX);wndClass.style = CS_DBLCLKS | CS_NOCLOSE | CS_VREDRAW | CS_HREDRAW;wndClass.lpfnWndProc = WndProc;wndClass.cbClsExtra = 0;wndClass.cbWndExtra = 0;wndClass.hInstance = hInstance;wndClass.hIcon = (HICON)::LoadImage(NULL, L"Image.ico", IMAGE_ICON, 0, 0,LR_DEFAULTSIZE | LR_LOADFROMFILE);wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);wndClass.lpszMenuName = NULL;wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";if (!RegisterClassEx(&wndClass))return -1;mhMainWnd = CreateWindow(L"ForTheDreamOfGameDevelop",WINDOW_TITLE,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);InitDirect3D();MoveWindow(mhMainWnd, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, true);ShowWindow(mhMainWnd, nShowCmd);UpdateWindow(mhMainWnd);MSG msg = { 0 };while (msg.message != WM_QUIT){if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)){TranslateMessage(&msg);DispatchMessage(&msg);}}UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance);return 0;
}LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_PAINT:ValidateRect(hwnd, NULL);break;case WM_KEYDOWN:if (wParam == VK_ESCAPE)DestroyWindow(hwnd);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hwnd, message, wParam, lParam);}return 0;
}
相关文章:
DirectX12_Windows_GameDevelop_3:Direct3D的初始化
引言 查看龙书时发现,第四章介绍预备知识的代码不太利于学习。因为它不像是LearnOpenGL那样从头开始一步一步教你敲代码,导致你没有一种整体感。如果你把它当作某一块的代码进行学习,你跟着敲会发现,总有几个变量是没有定义的。这…...
基于粒子群优化算法、鲸鱼算法、改进的淘沙骆驼模型算法(PSO/SSA/tGSSA)的微电网优化调度(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
数据分析篇-数据认知分析
一简介 数据认知分析,实际是对数据的整体结构和分布特征进行分析,是对整个数据外在的认识,也是数据分析的第一步。对于数据认知的分析,一般会考虑分散性、位置特性、变量的相关性等,一般会考虑平均数、方差、极差、峰…...
【力扣-每日一题】714. 买卖股票的最佳时机含手续费
class Solution { public:int maxProfit(vector<int>& prices, int fee) {//[i][0]-不持有 [i][1]-持有int mprices.size();vector<vector<int>> dp(m,vector<int>(2));dp[0][0]0; //初始状态dp[0][1]-prices[0];for(int i1;i<m;i){dp[i]…...
【代码实践】HAT代码Window平台下运行实践记录
HAT是CVPR2023上的自然图像超分辨率重建论文《activating More Pixels in Image Super-Resolution Transformer》所提出的模型。本文旨在记录在Window系统下运行该官方代码(https://github.com/XPixelGroup/HAT)的过程,中间会遇到一些问题&am…...
机器学习-Pytorch基础
Numpy和Pytorch可以相互转换,前者CPU上,后者GPU上,都是对矩阵进行运算,Pytorch的基本单位是张量。torch 可以初始化全为0、全为1、符合正态分布的矩阵确定性初始化 torch.tensor()torch.arrange()torch.linspace()torch.logspace…...
金九银十,刷完这个笔记,17K不能再少了....
大家好,最近有不少小伙伴在后台留言,得准备面试了,又不知道从何下手!为了帮大家节约时间,特意准备了一份面试相关的资料,内容非常的全面,真的可以好好补一补,希望大家在都能拿到理想…...
精确到区县级街道乡镇行政边界geojson格式矢量数据的获取拼接实现Echarts数据可视化大屏地理坐标信息地图的解决方案
在Echarts制作地理信息坐标地图时,最麻烦的就是街道乡镇级别的行政geojson的获取, 文件大小 788M 文件格式 .json格式,由于是大文件数据,无法直接使用记事本或者IDE编辑器打开,推荐Dadroit Viewer(国外…...
【Python 千题 —— 基础篇】多行输出
题目描述 下面是一道关于输入输出的基础题。⭐⭐⭐ 题目描述 编写一个Python程序,将字符串 Hello World! 存储在变量 str1 中,将字符串 Hello Python! 存储在变量 str2 中,然后使用 print 语句分别将它们在不同行打印出来。 输入描述 无…...
AdaBoost(上):数据分析 | 数据挖掘 | 十大算法之一
⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据…...
Py之pygraphviz:pygraphviz的简介、安装、使用方法之详细攻略
Py之pygraphviz:pygraphviz的简介、安装、使用方法之详细攻略 目录 pygraphviz的简介 pygraphviz的安装 Graphviz:可视化工具Graphviz的简介、安装、使用方法、经典案例之详细攻略 pygraphviz的使用方法 1、基础用法 2、进阶案例 Algorithm&#…...
acwing算法基础之基础算法--前缀和算法
目录 1 知识点2 模板 1 知识点 前缀后下标尽量从1开始,当然不从1开始也是ok的。 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an S 1 , S 2 , S 3 , . . . S n S_1,S_2,S_3,...S_n S1,S2,S3,...Sn S i S_i Si࿱…...
华为云云耀云服务器L实例评测|Ubuntu 22.04部署edusoho-ct企培版教程 | 支持华为云视频点播对接CDN加速
华为云云耀云服务器L实例评测|Ubuntu 22.04部署edusoho企培版教程 1、选择购买 华为云耀云服务器L实例 简单上云第一步 2、选择你要安装的操作系统,例如 Ubuntu 22.04 server 64bit 3、然后支付订单就行了 4、华为云云耀云服务器L实例创建好之后&#x…...
土木硕设计院在职转码上岸
一、个人介绍 双非土木硕,98年,目前在北京,职位为前端开发工程师,设计院在职期间自学转码上岸🌿 二、背景 本人于19年开始土木研究生生涯,研二期间去地产实习近半年(碧桂园和世茂,这两家的地产…...
js查询月份开始和结束日期
js查询月份开始和结束日期 月份开始和结束 月份开始和结束 整体不是很复杂,使用new Date()方法自带获取最后一天的时间 new Date(a,b,c),传递参数 参数a:是要获取的年份 参数b:是要获取的月份 参数c:是要获取的日期 传递日期为…...
mybatis开发部分核心代码
pom.xml<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 ht…...
Springboot中查看gradle工程使用了哪些仓库
在springboot项目开发中,由于初始化配置文件(init.gradle)可能存在多个目录中(不只一份),可能导致多次重复引入仓库。也有可能配置文件放置位置错误,导致gradle编译时找不到相应的仓库。如果能在编译时查看gradle到底引用了哪些库,…...
c#中的接口
使用IEnumerable统一迭代变量类型 class Program {static void Main(string[] args){int[] nums1 new int[] { 1, 2, 3, 4, 5 };ArrayList nums2 new ArrayList { 1, 2, 3, 4, 5 };Console.WriteLine(Sum(nums1));Console.WriteLine(Sum(nums2));Console.WriteLine(Avg(nums…...
老卫带你学---leetcode刷题(76. 最小覆盖子串)
76. 最小覆盖子串 问题: 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。 注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必…...
Maven-DskipTests和-Dmaven.test.skip=true的区别
DskipTeststrue和-Dmaven.test.skiptrue的区别 1、 -DskipTeststrue 不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下,如: mvn clean package -DskipTeststrue2、 -Dmaven.test.skiptrue 完全忽略测试代码的…...
conda中cuda、cuda-toolkit、cuda-nvcc、cuda-runtime的区别
conda中cuda、cuda-toolkit、cuda-nvcc、cuda-runtime的区别 cuda cuda-toolkit cuda-runtime cuda-toolkit 包含 cuda-nvcc CUDA cuda nvidia/label/cuda-11.8.0/linux-64::cuda-11.8.0-0 cuda-cccl nvidia/label/cuda-11.8.0/linux-64::cuda-cccl-11.8.89-0 cuda-comma…...
增强现实抬头显示AR-HUD
增强现实抬头显示(AR-HUD)可以将当前车身状态、障碍物提醒等信息3D投影在前挡风玻璃上,并通过自研的AR-Creator算法,融合实际道路场景进行导航,使驾驶员无需低头即可了解车辆实时行驶状况。结合DMS系统,可以…...
力扣-367.有效的完全平方数
暴力 class Solution { public:bool isPerfectSquare(int num) {for(long i 1; i * i < num; i) {if(i * i num) return true;}return false;} };二分查找 class Solution { public:bool isPerfectSquare(int num) {int left 1, right num;while(left < right) {in…...
小白必看!上位机控制单片机原理
嗨,大家好!今天,我们要探讨一个有趣的话题——"以上位机控制单片机"。不要担心,我们会用最简单的方式来解释这个概念。 首先,你可以把以上位机想象成一台超级聪明的电脑,就像你用来上网、玩游戏、…...
通过套接字手动写一个回显服务器吧
背景:程序员主要编写应用层的代码。真正要发送的数据需要上层协议调用下层协议,而应用层调用传输层时,传输层(系统内核)给应用层提供的一组API统称为Socket API。 系统提供给Java程序员的Socket API主要有两组: 基于UDP的API基于TCP的API目录 一、为什么需要网络编程?——…...
python读取CSV格式文件,遇到的问题20231007
python读取的CSV文件必须是具备相同列数的吗? 在Python中,读取CSV文件时不一定要求每一行都具有相同的列数。CSV文件可以包含不同数量的列,但你需要小心处理不同列数的情况,以确保代码能够正常处理。 通常情况下,CSV文…...
【面试题精讲】为什么重写equals时必须重写hashCode方法?
“ 有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top ” 首发博客地址[1] 面试题手册[2] 系列文章地址[3] equals() 方法用于比较两个对象是否相等,而 hashCode() 方法用于获取对象的哈希码…...
一文搞懂pytorch hook机制
pytorch的hook机制允许我们在不修改模型class的情况下,去debug backward、查看forward的activations和修改梯度。hook是一个在forward和backward计算时可以被执行的函数。在pytorch中,可以对Tensor和nn.Module添加hook。hook有两种类型,forwa…...
文本挖掘入门
文本挖掘的基础步骤 文本挖掘是从文本数据中提取有用信息的过程,通常包括文本预处理、特征提取和建模等步骤。以下是文本挖掘的基础入门步骤: 数据收集:首先,收集包含文本数据的数据集或文本文档。这可以是任何文本数据ÿ…...
【C++ techniques】Smart Pointers智能指针
Smart Pointers智能指针 看起来、用起来、感觉起来像内置指针,但提供更多的机能。拥有以下各种指针行为的控制权: 构造和析构;复制和赋值;解引。 Smart Pointers的构造、赋值、析构 C的标准程序库提供的auto_ptr template: au…...
网站开发如何让图片加载的更快/深圳网站建设系统
常量的定义 常量的值在编译时就已经确定常量的定义格式与变量基本相同等号右侧必须是常量或者常量表达式常量表达式中的函数必须是内置函数常量的初始化规则与枚举 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式使用相同的表达式不代表具有…...
备案网站简介怎么写/郑州企业网站优化排名
import timeprint time.strftime(%Y-%m-%d %H:%M:%S, time.localtime(time.time())) 转载于:https://www.cnblogs.com/huangshiyu13/p/7751657.html...
门户网站建设多少钱/运营推广渠道有哪些
效果: 下载 优化版下载: https://pan.baidu.com/s/1mvKGwJsBjXd2hvvi5Rp9pA 用法: import barrage from ../components/barrage.vue<barrage :linecount"3" :data"textlist"></barrage> //linecount属性表示显示几行弹幕 //textlist数据格式为…...
陕西企业营销型网站建设/网站权重怎么看
本文纯属个人见解,是对前面学习的总结,如有描述不正确的地方还请高手指正~ 按钮 Drop Down Control 按钮 -Circular Music Player Control 》》 Flat Pill Button 按钮类 --Fancy Menu 按钮之 每日一道理 我拽着春姑娘的衣裙,春姑娘把我带…...
宁波网站推广排名/优化方案官网
文章目录1. gitlab基本功能介绍2. gitlab安装3. gitlab汉化4. gitlab的基本使用4.1 用户和组和项目三者关系4.2 案例一4.3 案例二4.4 案例三5. gitlab备份恢复升级6. 小总结1. gitlab基本功能介绍 下载社区版gitlab路径:https://packages.gitlab.com/gitlab/gitlab…...
制作静态网站模板/贵阳百度推广电话
文章目录1、常用位运算2、使用场景3、使用位运算进行权限设置(路由或文件权限)3.1 作为路由管理1、常用位运算 符号描述运算规则(比较的是二进制)实例(以四位二进制数为例)&与两个位都为1时࿰…...