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.mzph.cn/news/96993.shtml

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

相关文章

乌班图22.04 kubeadm简单搭建k8s集群

1. 我遇到的问题 任何部署类问题实际上对于萌新来说都不算简单&#xff0c;因为没有经验&#xff0c;这里我简单将部署的步骤和想法给大家讲述一下 2. 简单安装步骤 准备 3台标准安装的乌班图server22.04&#xff08;采用vm虚拟机安装&#xff0c;ip为192.168.50.3&#xff0…

STC89C51基础及项目第10天:LCD显示字符(非标协议外设)

1. 初识LCD1602&#xff08;233.79&#xff09; 非标协议外设 LCD1602显示 LCD1602&#xff08;Liquid Crystal Display&#xff09;是一种工业字符型液晶&#xff0c;能够同时显示 1602 即 32 字符(16列两行) 引脚说明 第 1 脚&#xff1a; VSS 为电源地第 2 脚&#xff1…

SpringBoot项目默认使用HikariDataSource数据库连接池修改使用Druid连接池

1.启动项目&#xff0c;查看正在使用的链接池。 2.在pom.xml文件中引入驱动 <dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency> 3.在ap…

机器视觉工程师,公司设置奖金,真的为了奖励你吗?其实和你没关系

​据说某家大厂&#xff0c;超额罚款&#xff0c;有奖有罚很正常&#xff0c;但是我觉得你罚款代理商员工就不一样了&#xff0c;把代理商当成你的员工&#xff0c;我就觉得这些大厂的脑回路有问题。 有人从来没听说过项目奖金&#xff0c;更没有奖金。那么为什么设置奖金呢&a…

数字化转型频频失败?一体化模式提供新的思考

数字化连续6年出现在政府报告中&#xff0c;从《中小企业数字化赋能专项行动方案》到《关于推进“上云用数赋智”行动》、《“十四五” 规划和 2035 年远景目标建议》、《中小企业数字化转型指南》&#xff0c;再到2023年2月《数字中国建设整体布局规划》&#xff0c;加快数字化…

MM-Camera架构-ProcessCaptureRequest 流程分析

文章目录 processCaptureRequest\_3\_41.1 mDevice1.2 mDevice->ops->process\_capture\_request1.3 hardware to vendor mct\_shimlayer\_process\_event2.1 mct\_shimlayer\_handle\_parm2.2 mct\_shimlayer\_reg\_buffer processCaptureRequest_3_4 sdm660的摄像头走…

mysql5.7停止维护时间

mysql5.7将于2023年10月停止官网支持和更新&#xff1b;老项目要准备升级&#xff0c;新项目的mysql必须是mysql8.0&#xff08;2023-10&#xff09; 官方升级咨询地址 oracle官方升级咨询地址https://go.oracle.com/LP116153?elq_mid247718&sh1518132002061316121320310…

数据结构—栈、队列、链表

一、栈 Stack&#xff08;存取O(1)&#xff09; 先进后出&#xff0c;进去123&#xff0c;出来321。 基于数组&#xff1a;最后一位为栈尾&#xff0c;用于取操作。 基于链表&#xff1a;第一位为栈尾&#xff0c;用于取操作。 1.1、数组栈 /*** 基于数组实现的顺序栈&#…

波浪input输入框文字边框动画

一个input输入框的小动画,大家可以按需引入和修改 input的动画表现为,文字提示波浪动画 效果图如下 源码如下 tips: 有不懂的可以在评论区问博主 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name&q…

机器学习笔记(一)

1.线性回归模型 2. 损失函数 3.梯度下降算法 多元特征的线性回归 当有多个影响因素的时候,公式可以改写为: 当有多个影响因素的时候为了方便计算,可以使用 Numpy下面的点积方法, np.dot(w,x) 最后再加个b 就省略了很多书写步骤,这叫做矢量化 多元回归的梯度下降 左边是一…

unity脚本_生命周期函数 c#

帧&#xff1a;fps 即每秒钟跑的游戏帧数 游戏的本质 是一个死循环 每一次循环处理游戏逻辑就会更新一次画面 之所以能看到画面在动 是因为切换画面的速度达到一定时人眼就认为画面时流畅的 一帧就是执行一次循环 人眼舒适放松时可视帧数 24帧/s 游戏卡顿的原因&#xff1a; …

【将文本编码为图像灰度级别】以 ASCII 编码并与灰度级别位混合将文本字符串隐藏到图像像素的最低位中,使其不明显研究(Matlab代码实现)

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

土木硕设计院在职转码上岸

一、个人介绍 双非土木硕&#xff0c;98年&#xff0c;目前在北京&#xff0c;职位为前端开发工程师&#xff0c;设计院在职期间自学转码上岸&#x1f33f; 二、背景 本人于19年开始土木研究生生涯&#xff0c;研二期间去地产实习近半年(碧桂园和世茂&#xff0c;这两家的地产…

跨考408的C语言需要什么水平?

跨考408的C语言需要什么水平? 其实C语言了解一下就可以了&#xff0c;复习之前可以在b站上面随便找个视频看一下&#xff0c;指针部分重点学习一下就 行&#xff0c;C语言主要是数据结构代码部分的基础&#xff0c;对于跨考生来说&#xff0c;先看一下C语言对数据结构的复习有…

MySQL面试题合集

MySQL面经知识整理 文章目录 MySQL面经知识整理一、查询相关1.什么是MySQL的连接查询&#xff0c;左连接&#xff0c;右连接&#xff0c;内外连接2.SQL慢查询优化的方法3.大表查询如何优化 二、索引相关1.在MySQL中,可以通过哪些命令来查看查询是否使用了索引2.MySQL的最左匹配…

跨域请求方案整理实践

项目场景&#xff1a; 调用接口进行手机验证提示,项目需要调用其它域名的接口,导致前端提示跨域问题 问题描述 前端调用其他域名接口时报错提示: index.html#/StatisticalAnalysisOfVacancy:1 Access to XMLHttpRequest at http://xxxxx/CustomerService/template/examineMes…

UniApp创建项目HelloWorld

浏览器预览效果镇楼 普通项目创建 点击创建完成后&#xff0c;就如下所示 确实和微信小程序开发差不多。只是稍微换了一个名字的概念了&#xff0c;这个就是开发嘛&#xff0c;不要过于纠结概念性东西。开发开发&#xff0c;开了就知道怎么发了&#xff1f; 或许是 反正write就…

基于虚拟阻抗的下垂控制——孤岛双机并联Simulink仿真

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

Python3操作文件系列(二):文件数据读写|二进制数据读写

Python3操作文件系列(一):判断文件|目录是否存在三种方式 Python3操作文件系列(二):文件数据读写|二进制数据读写 Python3数据文件读取与写入 一: 文件数据|二进制数据读写 import os"""Python3的open(file,mode"文件的操作模式")利用该函数可以对…

基于腾讯云的OTA远程升级

一、OTA OTA即over the air,是一种远程固件升级技术&#xff0c;它允许在设备已经部署在现场运行时通过网络远程更新其固件或软件。OTA技术有许多优点&#xff0c;比如我们手机系统有个地方做了优化&#xff0c;使用OTA技术我们就不用召回每部手机&#xff0c;直接通过云端就可…