01. The-Forge 入门教程 - 绘制三角形
学习一个新的图形API或者中间件,第一件事一般就是绘制最简单的图元,三角形。
The-Forge API基于dx12/vulkan设计,如果曾经有学习过 DX12/Vulkan,上手会非常容易。当然如果没有接触过也没关系,使用过The-Forge之后,返回去上手DX12/Vulkan也会变得非常简单。如果只接触过openGL,那么这个教程对你可能会有些痛苦。openGL API的设计思路,和dx12/vulkan的差异还是太大了。
推荐一个vulkan教程: https://vulkan-tutorial.com/
新建窗口
本教程基于The-Forge第一个Demo 01_Transformations
精简而来,编译时选择 DebugDx 版本。The-Forge开发时没有使用CMake/premake/ninja等构建工具,而是手工维护各个平台的构建,因此,并没有use as lib的相关教程。对于对build,compile,link不熟悉的同学,这个步骤可能要有些痛苦了。
新建一个空工程
添加 The-Forge 根目录到 include path
添加 The-Forge\Examples_3\Unit_Tests\PC Visual Studio 2017\x64\DebugDx 到 library path
将下列库添加为依赖:Xinput9_1_0.libws2_32.libgainputstatic.libRendererDX12.libOS.lib
添加Preprocessor Definition: USE_MEMORY_TRACKING; _DEBUG ;_WINDOWS ;DIRECT3D12
做好这些准备工作之后,就可以开始写代码了

有了这个基础框架就可以运行了,运行之后应该是直接闪退,因为还缺少一些dll文件,把DebugDx下的 amd_ags_x64.dll;WinPixEventRunTime.dll;dxcompiler.dll;dxil.dll 拷贝到exe所在路径,然后再次运行,就可以看到窗口了

最后的extern和main函数,也可以用DEFINE_APPLICATION_MAIN代替。
程序生命周期
IApp.h中,有如下虚函数

查看WindowsBase.cpp中WindowsMain函数的实现,可以知道程序的实际生命周期如下

创建Renderer
添加一个全局变量: Renderer* pRenderer = nullptr;
在Init函数中添加:

在Exit函数中添加:removeRenderer(pRenderer);
Renderer对象的作用,类似dx12/vulkan中的device
创建GraphicsQueue
添加全局变量:Queue* pGraphicsQueue = nullptr;
在Init函数中添加:

在Exit函数中添加:removeQueue(pRenderer, pGraphicsQueue);

注意:在Init中创建对象的顺序,和Exit中销毁对象的顺序相反,也就是说,后创建的对象要更先销毁。
给只用过openGL的同学讲一下,在dx12/vulkan/metal等现代图形API中,device,fence,buffer,semaphore,descriptor等的管理,从驱动移交到了开发者手中,意味着开发者要做更多的事情。力量越大,责任就越大。
现代图形API一般支持多种Queue:Graphics,Compute,Cpoy(dx12)/Blit(Vulkan),不同的Queue意味的功能有所不同,比如Graphics Queue支持所有的Stage(VS,PS,DS,HS,GS,CS),而Compute Queue可能只支持CS一种Stage。实际开发时某些硬件运行开发者创建多个Queue,比如创建一个Graphics Queue和一个Compute Queue,在渲染的同时做一些通用计算任务,也就是所谓的asynccompute。
创建CmdPool,Cmd,Fence,Semaphore
添加全局变量:
const uint32_t gImageCount = 3;
CmdPool* pCmdPools[gImageCount] = { nullptr };
Cmd* pCmds[gImageCount] = { nullptr };
Fence* pRenderCompleteFences[gImageCount] = { nullptr };
Semaphore* pImageAcquiredSemaphore = nullptr;
Semaphore* pRenderCompleteSemaphores[gImageCount] = { nullptr };
在Init函数中添加:

在Exit函数中添加:

Cmd就是驱动中的command buffer,实际渲染时,可能要经常创建和销毁command buffer,因此设计了CmdPool,使用内存池来循环使用Cmd buffer。CmdPool/Fence/Semaphore的数量要大于等于交换链缓冲数量,保证资源没有冲突。
Fence和Semaphore则是用来控制状态同步的重要方式
fence:Fence提供了一种粗粒度的,从Device向Host单向传递信息的机制。在Queue submit的时候,可以附加带上一个Fence对象。之后Host就可以使用这个对象来查询之前提交的状态了
Semaphore:Semaphore用以同步不同的queue之间,或者同一个queue不同的submission之间的执行顺序。Semaphore只在Device上生效。也因此不同于fence,没有重置或者等待semaphore的api
创建Shader,VertexBuffer,RootSignature
添加全局变量:
Shader* pTriangleShader = nullptr;
Buffer* pTriangleVertexBuffer = nullptr;
RootSignature* pRootSignature = nullptr;
shader
在Init函数中添加:

在Exit函数中添加:removeShader(pRenderer, pTriangleShader);
vertex shader

pixel shader

shader文件拷贝到<exe目录>/shader/dx下
RootSignature
在Init函数中添加:

在Exit函数中添加:removeRootSignature(pRenderer, pRootSignature);
RootSignature是DX中的概念,用来告诉GPU该如何解析CPU传过来的buffer

等价于vulkan中的VkDescriptorSetLayout
VertexBuffer
在Init函数中添加:

在Exit函数中添加: removeResource(pTriangleVertexBuffer);
创建SwapChain
添加全局变量:SwapChain* pSwapChain = nullptr;
新建一个函数 addSwapChain :

在Load函数中添加:

在Unload函数中添加:removeSwapChain(pRenderer, pSwapChain);
注意,这次是Load和Unload函数
在Unload和Exit函数的第一行添加:waitQueueIdle(pGraphicsQueue);
SwapChain就是管理前后缓冲的对象。opengl的话,只需要调用 EGL/WGL/GLX... 提供的 swapBuffer 接口即可,但是在vulkan/dx12上,交换链的控制也转交给了开发者。
创建Pipeline
添加全局变量: Pipeline* pTrianglePipeline = nullptr;
在Load函数中添加:

在Unload函数中添加:removePipeline(pRenderer, pTrianglePipeline);
pipeline估计有dx使用经验的同学会比较熟悉,但是只有openGL经验的话可能就比较陌生了
Draw Triangle
经过了漫长的准备工作,终于到了绘制阶段了。
添加全局变量:uint32_t gFrameIndex = 0;
并在Draw函数中添加:gFrameIndex = (gFrameIndex + 1) % gImageCount;
整个程序并不需要在Update函数中实现功能
首先使用acquiescenceNextImage,获取backbuffer的RT

等待上一帧完成

Record command buffer

command buffer的内容在beginCmd和engCmd之间,可以分成bind RT,clear,draw triangle
接下来向Graphics queue提交刚刚生成的command buffer

queueSubmit会等acquireNextImage发出pImageAcquiredSemaphore信号后开始执行,在queueSubmit运行完成后,会发出renderCompleteSemaphore和renderCompleteFence信号
最后是present

present等待queueSubmit完成后发出的renderCompleteSemaphore信号
在OpenGL中,submit和present都在swapbuffer的过程中由驱动隐式完成
现在,点击运行,就可以看到你的第一个三角形
