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个步骤如下:
步骤序号步骤内容
1D3D12CreateDevice 函数创建 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/150524.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统及Docker安装RabbitMq

目录 一、linux系统安装 1、上传文件 2、在线安装依赖环境 3、安装Erlang 4、安装RabbitMQ 5、开启管理界面及配置 6、启动 7、删除mq 二、docker安装 1、上传mq.tar包或使用命令拉取镜像 2、启动并运行 3、访问mq 一、linux系统安装 1、上传文件 2、在线安装依赖环…

3. 无重复字符的最长子串(枚举+滑动窗口)

目录 一、题目 二、代码 一、题目 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 二、代码 class Solution { public:int lengthOfLongestSubstring(string s) {int _MaxLength 0;int left 0, right 0;vector<int>hash(128, 0);//ASCII…

Qt扫盲-QTreeView 理论总结

QTreeView 理论使用总结 一、概述二、快捷键绑定三、提高性能四、简单实例1. 设计与概念2. TreeItem类定义3. TreeItem类的实现4. TreeModel类定义5. TreeModel类实现6. 在模型中设置数据 一、概述 QTreeView实现了 model 中item的树形表示。这个类用于提供标准的层次列表&…

C#上位机——根据命令发送

C#上位机——根据命令发送 第一步&#xff1a;设置窗口的布局 第二步&#xff1a;设置各个属性 第三步&#xff1a;编写各个模块之间的关系

第九课 排序

文章目录 第九课 排序排序算法lc912.排序数组--中等题目描述代码展示 lc1122.数组的相对排序--简单题目描述代码展示 lc56.合并区间--中等题目描述代码展示 lc215.数组中的第k个最大元素--中等题目描述代码展示 acwing104.货仓选址--简单题目描述代码展示 lc493.翻转树--困难题…

OMV6 安装Extras 插件失败的解决方法

# Time: 2023/10/07 #Author: Xiaohong # 运行环境: OS: OMV6 # 功能: 安装Extras 插件失败的解决方法 问题描述&#xff1a;OMV6 安装插件omv-extras&#xff0c;只能按如下提示的命令行&#xff0c;但安装过程中&#xff0c;会提示raw.githubusercontent.com 无法访问插…

抖音账号矩阵系统开发源码----技术研发

一、技术自研框架开发背景&#xff1a; 抖音账号矩阵系统是一种基于数据分析和管理的全新平台&#xff0c;能够帮助用户更好地管理、扩展和营销抖音账号。 抖音账号矩阵系统开发源码 部分源码分享&#xff1a; ic function indexAction() { //面包屑 $breadc…

【QT5-程序控制电源-RS232-SCPI协议-上位机-基础样例【1】】

【QT5-程序控制电源-RS232-SCPI协议-上位机-基础样例【1】】 1、前言2、实验环境3、自我总结1、基础了解仪器控制-熟悉仪器2、连接SCPI协议3、选择控制方式-程控方式-RS2324、代码编写 4、熟悉协议-SCPI协议5、测试实验-测试指令&#xff08;1&#xff09;硬件连接&#xff08;…

再来介绍另一个binlog文件解析的第三方工具my2sql

看腻了文字就来听听视频演示吧&#xff1a;https://www.bilibili.com/video/BV1rp4y1w74B/ github项目&#xff1a;https://github.com/liuhr/my2sql gitee链接&#xff1a;https://gitee.com/mirrors/my2sql my2sql go版MySQL binlog解析工具&#xff0c;通过解析MySQL bin…

8.2 JUC - 4.Semaphore

目录 一、是什么&#xff1f;二、简单使用三、semaphore应用四、Semaphore原理 一、是什么&#xff1f; Semaphore&#xff1a;信号量&#xff0c;用来限制能同时访问共享资源的线程上限 二、简单使用 public class TestSemaphore {public static void main(String[] args) …

H桥级联型五电平三相逆变器Simulink仿真模型

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

我写过的最蠢的代码

目录 前言正文蠢代码 - 1蠢代码 - 2蠢代码 - 3提醒&#xff01; 蠢代码 - 4 总结 前言 每个人的身上都有毛毛 每个人的代码有些十分蠢&#xff0c;正巧&#xff0c;我也有&#xff01; 一看到CSDN的活动《你写过的最蠢的代码是&#xff1f;》&#xff0c;我立刻想要参加来告诉…

热迁移中VirtIO-PCI设备的配置空间处理

文章目录 问题现象定位过程日志分析源端目的端 原理分析基本原理上下文分析复现分析patch分析 总结解决方案 问题现象 集群升级虚拟化组件版本&#xff0c;升级前存量运行并挂载了virtio磁盘的虚拟机集群内热迁移到升级后的节点失败&#xff0c;QEMU报错如下&#xff1a; 202…

练[BJDCTF2020]EasySearch

[BJDCTF2020]EasySearch 文章目录 [BJDCTF2020]EasySearch掌握知识解题思路关键paylaod 掌握知识 ​ 目录扫描&#xff0c;index.php.swp文件泄露&#xff0c;代码审计&#xff0c;MD5区块爆破&#xff0c;请求响应包的隐藏信息&#xff0c;.shtml文件RCE漏洞利用 解题思路 …

Hive 【Hive(七)窗口函数练习】

窗口函数案例 数据准备 1&#xff09;建表语句 create table order_info (order_id string, --订单iduser_id string, -- 用户iduser_name string, -- 用户姓名order_date string, -- 下单日期order_amount int -- 订单金额 ); 2&#xff09;装载语句 i…

MongoDB集群管理

1、副本集-Replica Sets 1.1、简介 MongoDB中的副本集&#xff08;Replica Set&#xff09;是一组维护相同数据集的mongod服务。 副本集可提供冗余和高 可用性&#xff0c;是所有生产部署的基础。 也可以说&#xff0c;副本集类似于有自动故障恢复功能的主从集群。通俗的讲就…

基于安卓android微信小程序的旅游app系统

项目介绍 随着人民生活水平的提高,旅游业已经越来越大众化,而旅游业的核心是信息,不论是对旅游管理部门、对旅游企业,或是对旅游者而言,有效的获取旅游信息,都显得特别重要.自助定制游将使旅游相关信息管理工作规范化、信息化、程序化,提供旅游景点、旅游线路,旅游新闻等服务本…

根据中序与后序遍历结果构造二叉树

文章前言&#xff1a;对于中序与后序遍历不是太清楚的小白同学&#xff0c;作者推荐&#xff1a; 二叉树的初步认识_加瓦不加班的博客-CSDN博客 解题思路&#xff1a; 先通过后序遍历结果定位根节点 再结合中序遍历结果切分左右子树 代码实现&#xff1a; //1. pre-order 前…

网络和系统操作命令

目录 ping&#xff1a;用于检测网络是否通畅&#xff0c;以及网络时延情况。ipconfig&#xff1a;查看计算机的IP参数配置信息&#xff0c;如IP地址、默认网关、子网掩码等信息。netstat&#xff1a;显示协议统计信息和当前TCP/IP网络连接。tasklist&#xff1a;显示当前运行的…

接口测试复习Requests PyMysql Dubbo

一。基本概念 接口概念&#xff1a;系统与系统之间 数据交互的通道。 接⼝测试概念&#xff1a;校验 预期结果 与 实际结果 是否⼀致。 特征&#xff1a; 测试⻚⾯测试发现不了的问题。&#xff08;因为&#xff1a;接⼝测试 绕过前端界⾯。 &#xff09; 符合质量控制前移理…