DX渲染学习总结

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年,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版本都能使用相同接口。而做到这个的,就是市面上的游戏引擎,如下表:

在这里插入图片描述

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桌面窗口管理器),不同应用程序的数据输出,都作为它的输入,由它根据一定的规则,来展示到显示屏上。什么规则呢?简单的,就是像窗口程序一样,谁是顶层置顶窗口,谁就有优先显示权,这个置顶窗口就会将自我的尺寸全部展示出来。但是,通常窗口都不是占满全屏的,那么其他窗口,就会按照一个先后队列,依次展示出来(也可以理解为深度测试)。最终,根据队列中窗口区域,组合成最终的显示帧数据,供驱动传输给显示屏进行展示。
在这里插入图片描述

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

参考文章:
https://www.notion.so/willarun365/DX-313cb5cd66d349cd88ea07d17d6eb7b0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值