Keen的博客

记录所思、所想、所遇

欢迎来到我的个人站~


DX渲染学习总结

1. 环境准备

1.1. 兼容性

1.1.1. 硬件兼容性

这里的硬件,指的是显卡及显卡驱动。本身DirectX是一组ring3层和ring0层配合使用的工具组,它需要显卡及显卡驱动的支持,每一个DirectX版本,都需要相关显卡驱动版本的支持。而市面上的显卡及驱动程序,一般都设计成向下兼容,也就是对低版本具有良好的兼容性,DirectX的低版本也可以运行在最新的显卡上。

而驱动程序与Windows版本是对应的,因为DX不仅需要显卡硬件特性的支持,同时也需要图形驱动的支持,显卡自身的驱动与操作系统的图形驱动Win32K.sys联动,构成DX底层的技术支持,所以,考虑硬件的同时,也需要考虑操作系统版本上对DX的支持性。同样,操作系统对于DX的支持,也是向下兼容的

Windows中查看显卡硬件对DirectX版本特性的支持,可以通过dxdiag.exe来查看显卡对dx版本支持范围,如下:

png

右侧的功能级别,即表示支持的DirectX版本范围(这里是9.1~12.1)。注意,虽然这里最低只显示到9.1,但是实际上更低版本比如DX8,都是支持的。通常,需要关注这里版本范围的上限,我的parallel desktop就只支持到DX10。

所以,当我们编写代码时若想使用DX12最新特性,需要注意考虑显卡硬件设备对DX版本的支持,从代码层面增加if else分支逻辑。

市场上支持DX各种版本硬件及系统出现的时间,大致汇总一下如下:

总结一下:

  • 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也会有不一样的地方,这点极度坑爹。

代码示例:

2. 计算机图像显示原理

2.1. 图像显示基本原理

无论是讨论DX,还是OpenGL,还是GDI,本质是CPU、GPU、内存、显存、计算机总线、显示器相互作用来使数据进行呈现。

从最终端的显示屏来看,它要能显示数据,无非是显示屏有一个控制芯片,对应需要有一段显示屏内存数据区,以及一段与显示屏芯片通信的驱动程序。驱动程序负责将内存数据区的数据,发送往显示屏,而这段内存数据区,就是待显示的图像,相当于画板,由应用程序通过编码来填充绘制。与显示屏交互的驱动程序,只是做简单的动作:接收刷新信号(有图像已经准备好了,准备显示,由应用程序发出的通知),与显示屏控制器通过数据总线进行数据传递(显示屏控制芯片的特殊时钟信号来实现的),显示屏即有数据展示。它只做简单的事情,就意味着,只是数据的搬运工,而实际渲染数据内存区,还得是CPU或者GPU来绘制出来的。

那么如何产生一帧渲染数据呢?简单的一张静态图,当作一个二维数组,只需要CPU往二维数组里按位存储数据即可。而计算机通常是多任务的,也就是往往会有很多并行的渲染数据需要绘制,而显示屏资源实际只有一个,显示驱动同一时刻只负责运输一段数据帧,也就是前面说的那段内存数据区。计算机的多任务图像显示,也只是共用内存数据区,都往这段内存区写数据。如果每个人都想争做显示屏的展示内容,我写一段数据,然后你又写一段数据,那么很可能就相互冲突,展示的内容非你也非我。所以,要么同一时刻只有一个人写数据来展示,要么需要一个专门做数据最终输出的对象,由它来掌控最终产生到显示屏的内容应该是什么,它来接收多任务图像显示的数据输入。很明显是后者,因为前者只能让一幅图展示出来,相当于显示屏是被独占的,实际上,显示屏往往可以展示多个进程的窗口内容。既然是后者,就说明有一个专门的显示输出控制器(Vista之后实际就是DWM桌面窗口管理器),不同应用程序的数据输出,都作为它的输入,由它根据一定的规则,来展示到显示屏上。什么规则呢?简单的,就是像窗口程序一样,谁是顶层置顶窗口,谁就有优先显示权,这个置顶窗口就会将自我的尺寸全部展示出来。但是,通常窗口都不是占满全屏的,那么其他窗口,就会按照一个先后队列,依次展示出来(也可以理解为深度测试)。最终,根据队列中窗口区域,组合成最终的显示帧数据,供驱动传输给显示屏进行展示。

png

2.2. Windows图形系统架构

参考文章:https://docs.microsoft.com/zh-cn/previous-versions/ee921514(v=msdn.10)?redirectedfrom=MSDN

不仅DX在逐步升级,Windows图形架构也是逐步演变的。先总结一下基础流程架构的变化,如下:

png
png
png

有必要强调的几点:

  • 自从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驱动模型,接口操作引入更多更偏向硬件的操作

驱动架构演变如下:

png
png
png

2.3. 渲染管线

2.3.1. 渲染管线的基本作用

应用程序绘制一个窗口显示到桌面显示器,实际流程是比较简单的。所有显卡硬件、高清分辨率显示器以及Windows图形架构的升级,都只是为了趋向更逼真的电脑仿真,改善运行性能,让图形视频更流畅。而这个流畅,本质是,生产传输给Primary显示帧缓存一幅图像帧的速度更快,GPU的目的,就是为来使生产和传输过程变得更快。

何为传输更快?当然是为了更少的拷贝和更少的CPU操作(因为CPU是一个通用计算资源,不止图形在用,整个计算机都依赖它,所以资源是被所有应用共享)。

何为生产更快?简单的了解来一下图形学原理,在3D世界,我们依赖非常多的数学计算,包括:坐标系变换、批量像素点的位置矩阵变换、插值平滑变换等。一般在3D图形学编程中,是将由3D世界的众多三角形网格模型通过着色、变换、透视、裁剪、融合等,输出成2D显示器上的每一个像素点,整个流程是非常复杂,必将也需要非常多的计算资源。GPU的作用,简单来说,就是为了辅助CPU来做这些专门为图形数学变换而做的计算,整个过程,就是一次渲染过程,而这个过程是由渲染管线完成。

2.3.2. 渲染架构

为了有一个全面的认知,这里我总结了一下整个渲染管线与应用程序以及显示输出的关系,如下:

jpg

值得强调说明的点:

  • 渲染管线是被复用的,但是渲染的资源存储位置不同
  • 渲染管线输出的缓冲区,是跟窗口绑定的,因此逃脱不了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. 顶点着色器

主要是进行一系列的坐标变换,用下面一张图可以描述其作用

png

此阶段可编程,可以使用Vertex Shader.hlsl来进行着色器编码,主要是操作一堆坐标变换。

2.3.3.3. 曲面细分过程

图形学中,3D世界是由一堆三角形组成的网格模型来模拟的,而这个网格模型中三角形的数量决定了显示的质量。但是,三角形的数量越多,需要消耗的存储空间也就越大,设计也会更耗时。因此,往往取一个中间程度,剩下的过程,是交由曲面细分过程,在计算时通过插值平滑算法,自动细分三角形。整个过程可以通过下述图来表达作用

png

此阶段也是可编程的,可以细分为:外壳着色器阶段(Hull Shader)、镶嵌器阶段(Tessellator)和域着色器阶段(Domain Shader)。并且,它是可选阶段。

2.3.3.4. 几何着色器

它的输入是图元,可以将图元扩展成多边形,也是可选阶段,可编程,使用Geomitry Shader.hlsl编写。

2.3.3.5. 图元组装

将输入的顶点,组装成指定的图元,组装过程会进行裁剪和背面剔除,还会进行屏幕映射操作:透视除法和视口变换。这个阶段是硬件实现的。

2.3.3.6. 光栅化

主要是将前面阶段映射成的屏幕2D物体模型离散化成像素点,可用一张图表征作用:

png

此过程也是硬件自动实现的。

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);
}

3.7. 工程开源

工程参见:https://github.com/Keenjin/D3DSample

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少