1. 环境准备
1.1. 兼容性
1.1.1. 硬件兼容性
这里的硬件,指的是显卡及显卡驱动。本身DirectX是一组ring3层和ring0层配合使用的工具组,它需要显卡及显卡驱动的支持,每一个DirectX版本,都需要相关显卡驱动版本的支持。而市面上的显卡及驱动程序,一般都设计成向下兼容,也就是对低版本具有良好的兼容性,DirectX的低版本也可以运行在最新的显卡上。
而驱动程序与Windows版本是对应的,因为DX不仅需要显卡硬件特性的支持,同时也需要图形驱动的支持,显卡自身的驱动与操作系统的图形驱动Win32K.sys联动,构成DX底层的技术支持,所以,考虑硬件的同时,也需要考虑操作系统版本上对DX的支持性。同样,操作系统对于DX的支持,也是向下兼容的
Windows中查看显卡硬件对DirectX版本特性的支持,可以通过dxdiag.exe来查看显卡对dx版本支持范围,如下:
右侧的功能级别,即表示支持的DirectX版本范围(这里是9.1~12.1)。注意,虽然这里最低只显示到9.1,但是实际上更低版本比如DX8,都是支持的。通常,需要关注这里版本范围的上限,我的parallel desktop就只支持到DX10。
所以,当我们编写代码时若想使用DX12最新特性,需要注意考虑显卡硬件设备对DX版本的支持,从代码层面增加if else分支逻辑。
市场上支持DX各种版本硬件及系统出现的时间,大致汇总一下如下:
- 2002年8月,ATI公司推出全球第一款支持DX9显卡,此时还是WinXP时代
- 2006年11月,NVIDIA推出第一款DX10显卡,与此同时,2007年微软推出Vista系统,开始支持DX10
- 2009年9月,AMD-ATI推出全球首款DX11显卡,与此同时,2009年10月微软推出Win7系统
- 2015年7月,微软推出Win10,支持DX12,而硬件的话说是老卡也支持,此时是win10时代
总结一下:
- 2002年,WinXP,支持DX9
- 2006年,WinVista,支持DX9、DX10
- 2009年,Win7,支持DX9、DX10、DX11
- 2015年,Win10,支持DX9、DX10、DX11、DX12
1.1.2. 软件兼容性
由于DX版本是随着硬件和操作系统而逐步升级的,软件要想做到稳定运行在各种不同DX版本上,就需要拥有良好的兼容性。而软件自身如何做到兼容性的呢?
首先,DX9是覆盖现如今计算机系统xp ~ win10的,范围是最广的,所以大多数游戏厂商为了涉及更多的客户,选择DX9版作为游戏的渲染引擎(这也是为何市面上DX9的游戏编程的书最为广泛),最为简单方便,兼容性最好。
其次是DX11,因为市面上Win7以上用户,已经占比70%以上了,而从DX9跨越到DX11,性能上的优势肯定也是跨越级的,所以综合最佳选择是DX11。
当然,也会存在多版本并行的情况,为了兼顾XP~Win10,软件也会选择同时支持DX9、DX10、DX11、DX12全部或部分,兼容方式,采用随软件携带d3d9.dll、d3d10.dll、d3d11.dll、d3d12.dll,代码中根据环境检测来选择最佳引擎,代码调用层抽象出一层DX版本无关性逻辑层,这样也方便不同DX版本都能使用相同接口。而做到这个的,就是市面上的游戏引擎,如下表:
引擎名称 | Ogre | Unreal | Unity | Gamebryo | Bigworld |
---|---|---|---|---|---|
作者 | Steve streeting | Epic Games | Unity technology | Emegent game technology | Big World |
图形API | OpenGL DirecX | OpenGL DirectX Software | DirectX OpenGL | DirectX OpengGL | DirectX OpenGL |
操作系统 | Windows Linux MacOS | Windows Linux MacOS Xbox PlayStation GameCube | Windows Linux PlayStation iPhone Wii | Windows MaxOS Xbox360 PS3 | Windows 支持64位操作系统 |
开发语言 | C/C++ | C/C++ | C/C++ | C/C++ | C/C++ |
开源与否 | 公开 | 不公开 | 不公开 | 不公开 | 部分源代码开源 |
文档状况 | 充份 | 充份 | 提供逐步的指导、文档和实例方案 | 充份 | 充份 |
特征概貌 | 面向对象的设计 插件式结构 可存/取系统 | 面向对象的存取 插件式结构 可存取系统 | 面向对象设计 | 面向对象设计 开放式跨平台游戏引擎 插件式结构 可存取系统 | 面向对象的存取 插件式结构 可存取系统 |
支持脚本语言 | 脚本材质语言 允许代码外进行材质属性的设置 支持脚本控制的多遍渲染 | 内嵌的Unreal-Script语言 以及C++支持的所有形象华文本,支持非程序员编译器 | Unity支持3种脚本语言:JavaScript,C#,Boo | LUA脚本 | Python脚本 |
内嵌编辑器 | 不支持 | UnrealED:基于结构几何表示的实时设计工具,对光源、纹理、和几何体进行所见即所得的操作 脚本配置方式的编辑 | 完整优秀的编辑工具,包括地形,材质,动画,声音,模型等等 | 不支持 | 不支持 |
1.2. 安装说明
安装方法:
- DX11及以前版本,有独立安装包,下载链接https://www.microsoft.com/en-us/download/confirmation.aspx?id=6812
- DX12包含在Win10SDK中,一般下载WinSDKSetup进行安装,如果使用VS2015之后的版本,可以在VS2015插件集合中,选择Windows SDK 10版本进行安装,这里默认自带了DX的动态链接库和头文件。
注意:虽然第一种方法中同时包括DX9、DX10、DX11的开发SDK,但是会发现与Win10SDK中包含的内容不一样,比如可能winsdk中不包括d3dx9.h等,而且要注意,Win10 SDK各个不同版本对于DX同一个版本的sdk也会有不一样的地方,这点极度坑爹。
代码示例:
- 在上面第一种安装方法里,代码示例可以通过独立安装包中的应用程序:DirectX Sample Browser浏览。
- DX12的代码示例,则在github上:https://github.com/microsoft/DirectX-Graphics-Samples
- DX11的代码示例,虽然也可以通过DirectX Sample Browser浏览,但是在github上也存在新一份的代码https://github.com/walbourn/directx-sdk-samples,而且采用的写法与DX12相似
2. 计算机图像显示原理
2.1. 图像显示基本原理
无论是讨论DX,还是OpenGL,还是GDI,本质是CPU、GPU、内存、显存、计算机总线、显示器相互作用来使数据进行呈现。
从最终端的显示屏来看,它要能显示数据,无非是显示屏有一个控制芯片,对应需要有一段显示屏内存数据区,以及一段与显示屏芯片通信的驱动程序。驱动程序负责将内存数据区的数据,发送往显示屏,而这段内存数据区,就是待显示的图像,相当于画板,由应用程序通过编码来填充绘制。与显示屏交互的驱动程序,只是做简单的动作:接收刷新信号(有图像已经准备好了,准备显示,由应用程序发出的通知),与显示屏控制器通过数据总线进行数据传递(显示屏控制芯片的特殊时钟信号来实现的),显示屏即有数据展示。它只做简单的事情,就意味着,只是数据的搬运工,而实际渲染数据内存区,还得是CPU或者GPU来绘制出来的。
那么如何产生一帧渲染数据呢?简单的一张静态图,当作一个二维数组,只需要CPU往二维数组里按位存储数据即可。而计算机通常是多任务的,也就是往往会有很多并行的渲染数据需要绘制,而显示屏资源实际只有一个,显示驱动同一时刻只负责运输一段数据帧,也就是前面说的那段内存数据区。计算机的多任务图像显示,也只是共用内存数据区,都往这段内存区写数据。如果每个人都想争做显示屏的展示内容,我写一段数据,然后你又写一段数据,那么很可能就相互冲突,展示的内容非你也非我。所以,要么同一时刻只有一个人写数据来展示,要么需要一个专门做数据最终输出的对象,由它来掌控最终产生到显示屏的内容应该是什么,它来接收多任务图像显示的数据输入。很明显是后者,因为前者只能让一幅图展示出来,相当于显示屏是被独占的,实际上,显示屏往往可以展示多个进程的窗口内容。既然是后者,就说明有一个专门的显示输出控制器(Vista之后实际就是DWM桌面窗口管理器),不同应用程序的数据输出,都作为它的输入,由它根据一定的规则,来展示到显示屏上。什么规则呢?简单的,就是像窗口程序一样,谁是顶层置顶窗口,谁就有优先显示权,这个置顶窗口就会将自我的尺寸全部展示出来。但是,通常窗口都不是占满全屏的,那么其他窗口,就会按照一个先后队列,依次展示出来(也可以理解为深度测试)。最终,根据队列中窗口区域,组合成最终的显示帧数据,供驱动传输给显示屏进行展示。
2.2. Windows图形系统架构
参考文章:https://docs.microsoft.com/zh-cn/previous-versions/ee921514(v=msdn.10)?redirectedfrom=MSDN
不仅DX在逐步升级,Windows图形架构也是逐步演变的。先总结一下基础流程架构的变化,如下:
有必要强调的几点:
- 自从Vista转换到WDDM图形驱动模型后,引入DWM,桌面整体不再是2D显示区了,因为DWM本身使用DirectX技术,桌面实际上是一个全屏的3D程序,所以我们使用alt+tab来切换其他窗格时,看到来一个3D的炫酷切换界面。同时,渲染的窗口更加细腻,Aero毛玻璃效果也就很自然的引入其中。
- 另外,原来XP中的显示屏缓存为所有应用程序直接绘制,所以实际都是在一个缓冲区的不同区域绘制,如果拖动窗口很快,就会看到阴影。
- XP原来针对GDI使用XPDM进行硬件加速,后来换成Vista,GDI的绘制直接交由CPU来执行了,所以实际上,传统窗口程序在Vista上会比XP上更慢,Vista的CPU消耗也必然比XP更高
- Win10的DX12新架构本质上,与Win7的类似,最大的改变,是为了将性能提升到极致而升级WDDM驱动模型,接口操作引入更多更偏向硬件的操作
驱动架构演变如下:
2.3. 渲染管线
2.3.1. 渲染管线的基本作用
应用程序绘制一个窗口显示到桌面显示器,实际流程是比较简单的。所有显卡硬件、高清分辨率显示器以及Windows图形架构的升级,都只是为了趋向更逼真的电脑仿真,改善运行性能,让图形视频更流畅。而这个流畅,本质是,生产传输给Primary显示帧缓存一幅图像帧的速度更快,GPU的目的,就是为来使生产和传输过程变得更快。
何为传输更快?当然是为了更少的拷贝和更少的CPU操作(因为CPU是一个通用计算资源,不止图形在用,整个计算机都依赖它,所以资源是被所有应用共享)。
何为生产更快?简单的了解来一下图形学原理,在3D世界,我们依赖非常多的数学计算,包括:坐标系变换、批量像素点的位置矩阵变换、插值平滑变换等。一般在3D图形学编程中,是将由3D世界的众多三角形网格模型通过着色、变换、透视、裁剪、融合等,输出成2D显示器上的每一个像素点,整个流程是非常复杂,必将也需要非常多的计算资源。GPU的作用,简单来说,就是为了辅助CPU来做这些专门为图形数学变换而做的计算,整个过程,就是一次渲染过程,而这个过程是由渲染管线完成。
2.3.2. 渲染架构
为了有一个全面的认知,这里我总结了一下整个渲染管线与应用程序以及显示输出的关系,如下:
值得强调说明的点:
- 渲染管线是被复用的,但是渲染的资源存储位置不同
- 渲染管线输出的缓冲区,是跟窗口绑定的,因此逃脱不了DWM合成器的组合,但是因为窗口只是简单粗暴的根据Z序来消隐,所以它的计算量并不是很大。当然DWM同样也是使用DirectX来操作的,因此这里的性能就不是问题
- 我们的应用程序在执行一系列DirectX的调用,本质是往GPU的指令队列中打入一堆指令,GPU根据这些指令顺序执行某些事情,这些指令也都关联到具体的资源
2.3.3. 各个阶段简单说明
这里有一篇文章https://positiveczp.github.io/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF.pdf,介绍渲染管线非常详细。
2.3.3.1. 顶点数据输入
这里的顶点数据,包括:顶点坐标、顶点颜色、顶点法线、纹理坐标等数据,以及顶点图元信息:点(GL_POINTS)、线(GL_LINES)、线条(GL_LINE_STRIP)、三角面(GL_TRIANGLES)
2.3.3.2. 顶点着色器
主要是进行一系列的坐标变换,用下面一张图可以描述其作用
此阶段可编程,可以使用Vertex Shader.hlsl来进行着色器编码,主要是操作一堆坐标变换。
2.3.3.3. 曲面细分过程
图形学中,3D世界是由一堆三角形组成的网格模型来模拟的,而这个网格模型中三角形的数量决定了显示的质量。但是,三角形的数量越多,需要消耗的存储空间也就越大,设计也会更耗时。因此,往往取一个中间程度,剩下的过程,是交由曲面细分过程,在计算时通过插值平滑算法,自动细分三角形。整个过程可以通过下述图来表达作用
此阶段也是可编程的,可以细分为:外壳着色器阶段(Hull Shader)、镶嵌器阶段(Tessellator)和域着色器阶段(Domain Shader)。并且,它是可选阶段。
2.3.3.4. 几何着色器
它的输入是图元,可以将图元扩展成多边形,也是可选阶段,可编程,使用Geomitry Shader.hlsl编写。
2.3.3.5. 图元组装
将输入的顶点,组装成指定的图元,组装过程会进行裁剪和背面剔除,还会进行屏幕映射操作:透视除法和视口变换。这个阶段是硬件实现的。
2.3.3.6. 光栅化
主要是将前面阶段映射成的屏幕2D物体模型离散化成像素点,可用一张图表征作用:
此过程也是硬件自动实现的。
2.3.3.7. 片段着色器
片段着色器,又称像素着色器,可编程,通过Pixel Shalder.hlsl编写。这个阶段主要是进行光照变换和阴影处理,决定每一个像素的颜色值。
2.3.3.8. 测试混合
这个阶段分为测试和混合。
测试包括:裁切测试、Alpha测试、模板测试和深度测试,测试不通过的就会丢弃。
混合是指alpha混合,表示半透明效果,Aero毛玻璃效果就是利用了此alpha混合来实现的。
3. DX基本使用流程
3.1. 基于窗口的消息循环
HWND g_hWnd = NULL;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
{
Render();
// 这个表示清空绘制区,这样就不会重复产生WM_PAINT消息了
//(GetMessage或PeekMessage会将无绘制区的WM_PAINT从消息队列中移除)
ValidateRect(hWnd, NULL);
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3D9HELLOWORLD));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_D3D9HELLOWORLD);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
g_hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!g_hWnd)
{
return FALSE;
}
ShowWindow(g_hWnd, nCmdShow);
UpdateWindow(g_hWnd);
return TRUE;
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
InitD3D(g_hWnd);
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnInitD3D();
return (int) msg.wParam;
}
3.2. DX8
DX8示例:渲染一个三角形
- Step1:调用Direct3DCreate8获取d3d8 sdk接口对象
- Step2:获取IDirect3DDevice8某个屏幕显示器的设备对象
- Step3:设置渲染状态
- Step4:准备顶点数据,或者texture纹理数据
- Step5:BeginScene准备渲染
- Step6:SetStreamSource将顶点数据进行装配,SetVertexShader设置顶点格式
- Step7:DrawPrimitive传递顶点拓扑结构并绘制顶点图元,或者通过获取BackBuffer的表明,通过CopyRect可以拷贝纹理数据等,这一步就是往渲染目标后备缓存BackBuffer绘制数据
- Step8:EndScene结束渲染
- Step9:Present将BackBuffer交换到FrontBuffer,并调用视频流控制器,将FrontBuffer输出到屏幕
IDirect3D8* g_d3d8 = NULL;
IDirect3DDevice8* g_d3d8Device = NULL;
IDirect3DVertexBuffer8* g_pVB = NULL;
struct CUSTOMVERTEX
{
float x, y, z, rhw; // The position
D3DCOLOR color; // The color
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
void InitDevice(HWND hWnd)
{
g_d3d8 = Direct3DCreate8(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.hDeviceWindow = hWnd;
HRESULT hr = g_d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_d3d8Device);
g_d3d8Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_d3d8Device->SetRenderState(D3DRS_LIGHTING, FALSE);
g_d3d8Device->SetRenderState(D3DRS_ZENABLE, FALSE);
// 定义顶点
g_d3d8Device->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB);
CUSTOMVERTEX* pVertices;
if (FAILED(g_pVB->Lock(0, 0, (BYTE**)&pVertices, 0)))
{
return;
}
pVertices[0].x = 100.0f;
pVertices[0].y = 150.0f;
pVertices[0].z = 1.0f;
pVertices[0].rhw = 1.0f;
pVertices[0].color = D3DCOLOR_XRGB(255, 0, 0);
pVertices[1].x = 250.0f;
pVertices[1].y = 150.0f;
pVertices[1].z = 1.0f;
pVertices[1].rhw = 1.0f;
pVertices[1].color = D3DCOLOR_XRGB(0, 255, 0);
pVertices[2].x = 150.0f;
pVertices[2].y = 250.0f;
pVertices[2].z = 1.0f;
pVertices[2].rhw = 1.0f;
pVertices[2].color = D3DCOLOR_XRGB(0, 0, 255);
g_pVB->Unlock();
}
void Render()
{
if (g_d3d8Device && g_pVB)
{
g_d3d8Device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
g_d3d8Device->BeginScene();
g_d3d8Device->SetStreamSource(0, g_pVB, sizeof(CUSTOMVERTEX));
g_d3d8Device->SetVertexShader(D3DFVF_CUSTOMVERTEX);
g_d3d8Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1);
g_d3d8Device->EndScene();
g_d3d8Device->Present(0, 0, 0, 0);
}
}
void UnInitDevice()
{
if (g_pVB) g_pVB->Release();
if (g_d3d8Device) g_d3d8Device->Release();
if (g_d3d8) g_d3d8->Release();
}
3.3. DX9
DX9示例:加载绘制一张图
- Step1:调用Direct3DCreate9获取d3d9 sdk接口对象
- Step2:获取IDirect3DDevice9某个屏幕显示器的设备对象
- Step3:D3DXGetImageInfoFromFile获取文件信息
- Step4:CreateOffscreenPlainSurface创建一个离屏表面,有两种方法,一种是创建D3DPOOL_DEFAULT类型,存储在显卡,后面使用StrechRect到渲染目标RenderTarget,离屏表面大小可以不与渲染目标也即窗口客户区大小一致;另一种是创建D3DPOOL_SYSTEMMEM类型,存储在系统内存,后面使用UpdateSurface到渲染目标RenderTarget,离屏表面必须与渲染目标大小一致
- Step5:BeginScene准备渲染
- Step6:GetRenderTarget获取渲染目标
- Step7:根据Step4中创建离屏表面的类型,来决定使用StrechRect还是UpdateSurface方法渲染到RenderTarget
- Step8:EndScene结束渲染
- Step9:Present将BackBuffer交换到FrontBuffer,并调用视频流控制器,将FrontBuffer输出到屏幕
IDirect3D9Ex* g_pD3D9Ex = NULL;
IDirect3DDevice9Ex* g_pD3D9DeviceEx = NULL;
IDirect3DSurface9* g_pImgFromFile = NULL;
void InitDevice(HWND hWnd)
{
HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &g_pD3D9Ex);
if (FAILED(hr))
{
ATL::CAtlString strErr;
strErr.Format(L"errcode: 0x%0x", hr);
MessageBoxW(hWnd, L"Direct3DCreate9Ex failed. " + strErr, NULL, MB_OK);
return;
}
RECT rcWnd;
GetClientRect(hWnd, &rcWnd);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.hDeviceWindow = hWnd;
//d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
if (FAILED(hr = g_pD3D9Ex->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, // 软件顶点处理。如果显卡支持硬件顶点处理,最好是使用显卡
&d3dpp, NULL, &g_pD3D9DeviceEx)))
{
return;
}
D3DXIMAGE_INFO ii;
D3DXGetImageInfoFromFile(L"G:\\Solution\\Debug\\test.jpg", &ii);
g_pD3D9DeviceEx->CreateOffscreenPlainSurface(rcWnd.right-rcWnd.left, rcWnd.bottom-rcWnd.top, ii.Format, D3DPOOL_SYSTEMMEM, &g_pImgFromFile, NULL);
// g_pD3D9DeviceEx->CreateOffscreenPlainSurface(ii.Width, ii.Height, ii.Format, D3DPOOL_DEFAULT, &g_pImgFromFile, NULL);
D3DXLoadSurfaceFromFileW(g_pImgFromFile, NULL, NULL, L"G:\\Solution\\Debug\\test.jpg", NULL, D3DX_FILTER_NONE, 0, NULL);
}
void Render()
{
if (g_pD3D9DeviceEx && g_pImgFromFile)
{
g_pD3D9DeviceEx->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
if (SUCCEEDED(g_pD3D9DeviceEx->BeginScene()))
{
IDirect3DSurface9* pBackBuffer = NULL;
//g_pD3D9DeviceEx->GetBackBuffer(0,0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
g_pD3D9DeviceEx->GetRenderTarget(0, &pBackBuffer);
//g_pD3D9DeviceEx->StretchRect(g_pImgFromFile, NULL, pBackBuffer, NULL, D3DTEXTUREFILTERTYPE::D3DTEXF_NONE);
g_pD3D9DeviceEx->UpdateSurface(g_pImgFromFile, NULL, pBackBuffer, NULL);
pBackBuffer->Release();
g_pD3D9DeviceEx->EndScene();
}
g_pD3D9DeviceEx->PresentEx(0, 0, 0, 0, 0);
}
}
void UnInitDevice()
{
if (g_pImgFromFile) g_pImgFromFile->Release();
if (g_pD3D9DeviceEx) g_pD3D9DeviceEx->Release();
if (g_pD3D9Ex) g_pD3D9Ex->Release();
}
3.4. DX10
DX10示例:绘制一个三角形
- Step1:D3D10CreateDeviceAndSwapChain创建设备和交换链系统。
- Step2:CreateRenderTargetView和OMSetRenderTargets创建渲染目标视图缓存,绑定到交换系统上,作为渲染目标
- Step3:设置ViewPort
- Step4:HLSL语言定义顶点着色器和像素着色器
- Step5:D3DReadFileToBlob和CreateVertexShader加载顶点着色器,D3DReadFileToBlob和CreatePixelShader加载像素着色器,VSSetShader和PSSetShader分别设置将顶点着色器和像素着色器绑定到渲染管线
- Step6:定义输入布局结构D3D10_INPUT_ELEMENT_DESC,CreateInputLayout和IASetInputLayout装配输入布局结构。输入布局结构可以包括顶点属性(包括坐标位置、颜色等)、纹理属性等
- Step7:定义顶点数据,CreateBuffer创建Buffer资源,用于存储顶点数据
- Step8:通过IASetVertexBuffers装配顶点缓存,并通过IASetPrimitiveTopology设置顶点拓扑结构
- Step9:Draw绘制
// Header.hlsli
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
};
// VertexShader.hlsl
#include "Header.hlsli"
// 顶点着色器
VertexOut VS(VertexIn vIn)
{
VertexOut vOut;
vOut.posH = float4(vIn.pos, 1.0f);
vOut.color = vIn.color; // 这里alpha通道的值默认为1.0
return vOut;
}
// PixelSHader.hlsl
#include "Header.hlsli"
// 像素着色器
float4 PS(VertexOut pIn) : SV_Target
{
return pIn.color;
}
// CPP
#include <d3d10.h>
#include <d3dx10.h>
#include <d3dcompiler.h>
#include <directxmath.h>
ID3D10Device* g_pd3dDevice = NULL;
IDXGISwapChain* g_pSwapChain = NULL;
ID3D10RenderTargetView* g_pRenderTargetView = NULL;
ID3D10InputLayout* g_pVertexLayout = NULL;
ID3D10Buffer* g_pVertexBuffer = NULL;
ID3D10VertexShader* g_pVertexShader = NULL;
ID3D10PixelShader* g_pPixelShader = NULL;
struct SimpleVertex
{
D3DXVECTOR3 pos;
D3DXVECTOR4 color;
};
void InitDevice(HWND hWnd)
{
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = 640;
sd.BufferDesc.Height = 480;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
if (FAILED(D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_REFERENCE, NULL,
0, D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice)))
{
return;
}
// Create a render target view
ID3D10Texture2D* pBackBuffer;
if (FAILED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer)))
return;
HRESULT hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
pBackBuffer->Release();
if (FAILED(hr))
return;
g_pd3dDevice->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);
D3D10_VIEWPORT vp;
vp.Width = 640;
vp.Height = 480;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pd3dDevice->RSSetViewports(1, &vp);
// Define the input layout
D3D10_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = sizeof(layout) / sizeof(layout[0]);
// Create the input layout
ID3DBlob* blob;
D3DReadFileToBlob(L"G:\\Solution\\Debug\\VertexShader.cso", &blob);
g_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), &g_pVertexShader);
g_pd3dDevice->CreateInputLayout(layout, numElements,
blob->GetBufferPointer(), blob->GetBufferSize(), &g_pVertexLayout);
blob->Release();
// Set the input layout
g_pd3dDevice->IASetInputLayout(g_pVertexLayout);
D3DReadFileToBlob(L"G:\\Solution\\Debug\\PixelShader.cso", &blob);
g_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), &g_pPixelShader);
blob->Release();
g_pd3dDevice->VSSetShader(g_pVertexShader);
g_pd3dDevice->PSSetShader(g_pPixelShader);
// Create vertex buffer
SimpleVertex vertices[] =
{
{ D3DXVECTOR3(0.0f, 0.5f, 0.5f), D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f) },
{ D3DXVECTOR3(0.5f, -0.5f, 0.5f), D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f) },
{ D3DXVECTOR3(-0.5f, -0.5f, 0.5f), D3DXVECTOR4(0.0f, 0.0f, 1.0f, 1.0f) },
};
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DEFAULT;
bd.ByteWidth = sizeof(SimpleVertex) * 3;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D10_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
if (FAILED(g_pd3dDevice->CreateBuffer(&bd, &InitData, &g_pVertexBuffer)))
return;
// Set vertex buffer
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
g_pd3dDevice->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
// Set primitive topology
g_pd3dDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}
void Render()
{
if (g_pd3dDevice && g_pSwapChain)
{
float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA
g_pd3dDevice->ClearRenderTargetView(g_pRenderTargetView, ClearColor);
// Render a triangle
g_pd3dDevice->Draw(3, 0);
g_pSwapChain->Present(0, 0);
}
}
void UnInitDevice()
{
if (g_pVertexBuffer) g_pVertexBuffer->Release();
if (g_pVertexLayout) g_pVertexLayout->Release();
if (g_pVertexShader) g_pVertexShader->Release();
if (g_pRenderTargetView) g_pRenderTargetView->Release();
if (g_pd3dDevice) g_pd3dDevice->Release();
if (g_pSwapChain) g_pSwapChain->Release();
}
3.5. DX11
DX11示例:绘制一个三角形
同DX10,只是Device与渲染阶段相关的内容,全部挪到DeviceContext中
// CPP
#include <d3d11.h>
#include <d3dcompiler.h>
#include <directxmath.h>
ID3D11Device* g_pd3dDevice = NULL;
IDXGISwapChain* g_pSwapChain = NULL;
ID3D11DeviceContext* g_pd3dImmediateContext = NULL;
ID3D11RenderTargetView* g_pRenderTargetView = NULL;
ID3D11VertexShader* g_pVertexShader = NULL;
ID3D11PixelShader* g_pPixelShader = NULL;
ID3D11InputLayout* g_pVertexLayout = NULL;
ID3D11Buffer* g_pVertexBuffer = NULL;
struct SimpleVertex
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT4 color;
};
void InitDevice(HWND hWnd)
{
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = 640;
sd.BufferDesc.Height = 480;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, NULL, &g_pd3dImmediateContext);
// Create a render target view
ID3D11Texture2D* pBackBuffer;
if (FAILED(g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer)))
return;
HRESULT hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView);
pBackBuffer->Release();
if (FAILED(hr))
return;
g_pd3dImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL);
D3D11_VIEWPORT vp;
vp.Width = 640;
vp.Height = 480;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
g_pd3dImmediateContext->RSSetViewports(1, &vp);
// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = sizeof(layout) / sizeof(layout[0]);
// Create the input layout
ID3DBlob* blob;
D3DReadFileToBlob(L"G:\\Solution\\Debug\\VertexShader.cso", &blob);
g_pd3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), NULL, &g_pVertexShader);
// 创建顶点布局
g_pd3dDevice->CreateInputLayout(layout, numElements,
blob->GetBufferPointer(), blob->GetBufferSize(), &g_pVertexLayout);
blob->Release();
// Set the input layout
g_pd3dImmediateContext->IASetInputLayout(g_pVertexLayout);
D3DReadFileToBlob(L"G:\\Solution\\Debug\\PixelShader.cso", &blob);
g_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), NULL, &g_pPixelShader);
blob->Release();
g_pd3dImmediateContext->VSSetShader(g_pVertexShader, NULL, 0);
g_pd3dImmediateContext->PSSetShader(g_pPixelShader, NULL, 0);
// Create vertex buffer
SimpleVertex vertices[] =
{
{ DirectX::XMFLOAT3(0.0f, 0.5f, 0.5f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ DirectX::XMFLOAT3(0.5f, -0.5f, 0.5f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ DirectX::XMFLOAT3(-0.5f, -0.5f, 0.5f), DirectX::XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
};
D3D11_BUFFER_DESC bd;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(SimpleVertex) * 3;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = vertices;
if (FAILED(g_pd3dDevice->CreateBuffer(&bd, &InitData, &g_pVertexBuffer)))
return;
// Set vertex buffer
UINT stride = sizeof(SimpleVertex);
UINT offset = 0;
g_pd3dImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset);
// Set primitive topology
g_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}
void Render()
{
//
// Clear the backbuffer
//
if (g_pd3dDevice && g_pSwapChain)
{
float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA
g_pd3dImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);
// Render a triangle
g_pd3dImmediateContext->Draw(3, 0);
g_pSwapChain->Present(0, 0);
}
}
void UnInitDevice()
{
if (g_pVertexBuffer) g_pVertexBuffer->Release();
if (g_pVertexLayout) g_pVertexLayout->Release();
if (g_pVertexShader) g_pVertexShader->Release();
if (g_pRenderTargetView) g_pRenderTargetView->Release();
if (g_pd3dDevice) g_pd3dDevice->Release();
if (g_pd3dImmediateContext) g_pd3dImmediateContext->Release();
if (g_pSwapChain) g_pSwapChain->Release();
}
3.6. DX12
DX12示例:绘制一个三角形
- Step1:图形调试器支持
- Step2:创建设备
- Step3:创建命令队列(GPU和CPU通信,使用一堆命令队列依次执行,是否可以理解为,CPU和GPU通信方式修改成了异步方式了?)
- Step4:创建交换链。注意,必须至少创建两个FrameBuffer
- Step5:创建渲染器目标视图(RTV) 描述符堆,类似一个资源池,可以用来分配给渲染目标等。
- Step6:创建帧资源(每个帧的渲染器目标视图),每次创建,都需要找描述符堆申请资源
- Step7:创建命令分配器,管理命令列表
- Step8:创建根签名,类似签名证书根证书一样,资源一旦“打”上这个签名(结构体中指向根签名对象),就表示要进入渲染管道进行渲染使用
- Step9:编译着色器
- Step10:定义顶点输入布局、裁剪区、资源屏障
- Step11:创建图形管道状态对象,图形管道状态对象包括顶点着色器、像素着色器、顶点输入布局、顶点拓扑结构、渲染目标格式等
- Step12:创建命令列表
- Step13:创建并加载顶点缓冲区
- Step14:创建顶点缓冲区视图
- Step15:创建fence,用于GPU和CPU同步
- Step16:等待GPU完成准备工作
- Step17:填充命令列表,包括:绑定根签名、渲染目标、视图、顶点缓存、绘制命令
- Step18:执行命令列表
- Step19:触发swapchain,Present显示帧
- Step20:等待GPU完成动作
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;
result.position = position;
result.color = color;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}
#include <wrl.h>
using Microsoft::WRL::ComPtr;
#include "d3dx12.h"
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <directxmath.h>
#include <stdexcept>
ComPtr<ID3D12Device> g_pd3dDevice;
ComPtr<ID3D12CommandQueue> g_pd3dCommandQueue;
ComPtr<IDXGISwapChain3> g_pd3dSwapChain;
ComPtr<ID3D12DescriptorHeap> g_prtvDescHeap;
UINT g_rtvDescriptorSize = 0;
ComPtr<ID3D12Resource> g_pRenderTarget[2];
ComPtr<ID3D12CommandAllocator> g_pd3dCommandAllocator;
ComPtr<ID3D12RootSignature> g_rootSign;
ComPtr<ID3D12GraphicsCommandList> g_commandList;
ComPtr<ID3D12PipelineState> g_pipelineState;
ComPtr<ID3D12Resource> g_pVB;
D3D12_VERTEX_BUFFER_VIEW g_vertexBufferView;
ComPtr<ID3D12Fence> g_pFence;
UINT64 g_fenceValue = 0;
HANDLE g_hfenceEvent = NULL;
CD3DX12_VIEWPORT g_viewport;
CD3DX12_RECT g_scissorRect;
UINT g_frameCnt = 2;
UINT g_frameIndex = 0;
struct Vertex
{
DirectX::XMFLOAT3 position;
DirectX::XMFLOAT4 color;
};
inline std::string HrToString(HRESULT hr)
{
char s_str[64] = {};
sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));
return std::string(s_str);
}
class HrException : public std::runtime_error
{
public:
HrException(HRESULT hr) : std::runtime_error(HrToString(hr)), m_hr(hr) {}
HRESULT Error() const { return m_hr; }
private:
const HRESULT m_hr;
};
void ThrowIfFailed(HRESULT hr)
{
if (FAILED(hr))
{
throw HrException(hr);
}
}
void GetHardwareAdapter(IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter)
{
ComPtr<IDXGIAdapter1> adapter;
*ppAdapter = nullptr;
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
// If you want a software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don't create the
// actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
break;
}
}
*ppAdapter = adapter.Detach();
}
void LoadPipeline(HWND hWnd)
{
UINT dxgiFactoryFlags = 0;
#if defined(_DEBUG)
// Step1:图形调试器支持
// Enable the debug layer (requires the Graphics Tools "optional feature").
// NOTE: Enabling the debug layer after device creation will invalidate the active device.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// Enable additional debug layers.
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
// Step2:创建设备
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));
ComPtr<IDXGIAdapter1> hardwareAdapter;
GetHardwareAdapter(factory.Get(), &hardwareAdapter);
D3D12CreateDevice(
hardwareAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&g_pd3dDevice)
);
// Step3:创建命令队列(GPU和CPU通信,使用一堆命令队列依次执行,是否可以理解为,CPU和GPU通信方式修改成了异步方式了?)
// Describe and create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
g_pd3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&g_pd3dCommandQueue));
// Step4:创建交换链
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = g_frameCnt; // 至少有两个
swapChainDesc.Width = 640;
swapChainDesc.Height = 480;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
ComPtr<IDXGISwapChain1> swapChain;
ThrowIfFailed(factory->CreateSwapChainForHwnd(
g_pd3dCommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
hWnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain
));
swapChain.As(&g_pd3dSwapChain);
// Step5:创建渲染器目标视图(RTV) 描述符堆,类似一个资源池,可以用来分配给渲染目标等。
// Describe and create a render target view (RTV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = g_frameCnt;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
g_pd3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&g_prtvDescHeap));
g_rtvDescriptorSize = g_pd3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
// Step6:创建帧资源(每个帧的渲染器目标视图),每次创建,都需要找描述符堆申请资源
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_prtvDescHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT n = 0; n < g_frameCnt; n++)
{
ThrowIfFailed(g_pd3dSwapChain->GetBuffer(n, IID_PPV_ARGS(&g_pRenderTarget[n])));
g_pd3dDevice->CreateRenderTargetView(g_pRenderTarget[n].Get(), nullptr, rtvHandle);
rtvHandle.Offset(1, g_rtvDescriptorSize);
}
// Step7:创建命令分配器,管理命令列表
g_pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_pd3dCommandAllocator));
}
void LoadAssets()
{
// Step8:创建根签名,类似签名证书根证书一样,资源一旦“打”上这个签名(结构体中指向根签名对象),就表示要进入渲染管道进行渲染使用
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
g_pd3dDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&g_rootSign));
// Step9:编译着色器
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
ThrowIfFailed(D3DCompileFromFile(L"G:\\Solution\\D3D12Test\\shader.hlsl", nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr));
ThrowIfFailed(D3DCompileFromFile(L"G:\\Solution\\D3D12Test\\shader.hlsl", nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr));
// Step10:定义顶点输入布局
// Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
g_viewport.TopLeftX = 100.0f;
g_viewport.TopLeftY = 10.0f;
g_viewport.Width = 400.0f;
g_viewport.Height = 250.0f;
g_scissorRect.left = 0.0f;
g_scissorRect.top = 0.0f;
g_scissorRect.right = 640.0f;
g_scissorRect.bottom = 480.0f;
// Step11:创建图形管道状态对象,图形管道状态对象包括顶点着色器、像素着色器、顶点输入布局、顶点拓扑结构、渲染目标格式等
// Describe and create the graphics pipeline state object (PSO).
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = g_rootSign.Get();
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
g_pd3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&g_pipelineState));
// Step12:创建命令列表
g_pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_pd3dCommandAllocator.Get(), g_pipelineState.Get(), IID_PPV_ARGS(&g_commandList));
// Command lists are created in the recording state, but there is nothing
// to record yet. The main loop expects it to be closed, so close it now.
g_commandList->Close();
// Step13:创建并加载顶点缓冲区
Vertex triangleVertices[] =
{
{ { 0.0f, 0.25f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.25f, -0.25f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.25f, -0.25f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};
const UINT vertexBufferSize = sizeof(triangleVertices);
// Note: using upload heaps to transfer static data like vert buffers is not
// recommended. Every time the GPU needs it, the upload heap will be marshalled
// over. Please read up on Default Heap usage. An upload heap is used here for
// code simplicity and because there are very few verts to actually transfer.
g_pd3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&g_pVB));
// Copy the triangle data to the vertex buffer.
UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
g_pVB->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
g_pVB->Unmap(0, nullptr);
// Step14:创建顶点缓冲区视图
// Initialize the vertex buffer view.
g_vertexBufferView.BufferLocation = g_pVB->GetGPUVirtualAddress();
g_vertexBufferView.StrideInBytes = sizeof(Vertex);
g_vertexBufferView.SizeInBytes = vertexBufferSize;
// Step15:创建fence,用于GPU和CPU同步
g_pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_pFence));
g_fenceValue = 1;
// Create an event handle to use for frame synchronization.
g_hfenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (g_hfenceEvent == nullptr)
{
return;
}
// Step16:等待GPU完成准备工作
// Wait for the command list to execute; we are reusing the same command
// list in our main loop but for now, we just want to wait for setup to
// complete before continuing.
WaitForPreviousFrame();
}
void InitDevice(HWND hWnd)
{
// 加载管线
LoadPipeline(hWnd);
// 记载资产
LoadAssets();
}
void WaitForPreviousFrame()
{
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
// Signal and increment the fence value.
const UINT64 fence = g_fenceValue;
g_pd3dCommandQueue->Signal(g_pFence.Get(), fence);
g_fenceValue++;
// Wait until the previous frame is finished.
if (g_pFence->GetCompletedValue() < fence)
{
g_pFence->SetEventOnCompletion(fence, g_hfenceEvent);
WaitForSingleObject(g_hfenceEvent, INFINITE);
}
g_frameIndex = g_pd3dSwapChain->GetCurrentBackBufferIndex();
}
void PopulateCommandList()
{
// Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress.
g_pd3dCommandAllocator->Reset();
// However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before
// re-recording.
g_commandList->Reset(g_pd3dCommandAllocator.Get(), g_pipelineState.Get());
// Set necessary state.
g_commandList->SetGraphicsRootSignature(g_rootSign.Get());
g_commandList->RSSetViewports(1, &g_viewport);
g_commandList->RSSetScissorRects(1, &g_scissorRect);
// Indicate that the back buffer will be used as a render target.
g_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(g_pRenderTarget[g_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_prtvDescHeap->GetCPUDescriptorHandleForHeapStart(), 0, g_rtvDescriptorSize);
g_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// Record commands.
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
g_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
g_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
g_commandList->IASetVertexBuffers(0, 1, &g_vertexBufferView);
g_commandList->DrawInstanced(3, 1, 0, 0);
// Indicate that the back buffer will now be used to present.
g_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(g_pRenderTarget[g_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
g_commandList->Close();
}
void Render()
{
if (!g_pFence)
{
return;
}
// Step17:填充命令列表
PopulateCommandList();
// Step18:执行命令列表
ID3D12CommandList* ppCommandLists[] = { g_commandList.Get() };
g_pd3dCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Step19:触发swapchain,显示帧
g_pd3dSwapChain->Present(1, 0);
// Step20:等待GPU完成动作
WaitForPreviousFrame();
}
void UnInitDevice()
{
// Ensure that the GPU is no longer referencing resources that are about to be
// cleaned up by the destructor.
WaitForPreviousFrame();
CloseHandle(g_hfenceEvent);
}