上一篇我们在Windows上用OpenGL 4.0版本的接口实现了一个旋转的立方体(当然,实现这个功能实际上并没有使用什么4.0级别的特性。图形API规格和功能级别是两个概念)
到此为止我们应该对于一个基本的画面绘制流程有一定的了解了。虽然我们还没有涉及到诸如贴图光照,以及曲面细分,异步计算等概念,但是作为整个渲染管道的骨架已经基本搭建好了。
接下来让我们一起看看图形API的最前沿,DirectX 12和Vulkan。因为我们要写的是次世代引擎,我们需要考虑使用这些最新的图形API。
我们之所以没有从最新的API开始,因为我们有个学习的过程。图形API不是一步发展到今天这个样子的,我们需要了解这个过程。这不仅能缓和我们的学习曲线,更能让我们看清发展的走向。而能否正确预测这个走向,是评价一个架构好坏的重要基准之一。
DX12我参考的主要资料是【*1】Tutorial: Migrating Your Apps to DirectX* 12 – Part 1
— (题外话开始)–
很意外地,看到了Lv Wenwei的名字。他是蜗牛的技术总监,我2014年刚刚进入SIE的第一个任务,就是到苏州去支持他们移植开发《九阳神功》。虽然那个时候我其实刚进公司,对PS4开发基本一无所知。
吕老师谈吐很儒雅,技术看上去也相当不错。3年前的故事最终是以我把SIE日本的台湾人老师(也是我在SIE的导师)请来救场结束。
— (题外话结束)–
我们开始编码。我是一边看着【*2】Creating a basic Direct3D 12 component 一边升级我的代码的。
由于我们的代码已经比较长了。从本篇起我将不再贴出所有的代码。完整的代码请到GitHub上面去下载。本篇对应的branch为article_15。
首先我们是替换头文件。
// include the basic windows header file #include <windows.h> #include <windowsx.h> +#include <stdio.h> #include <tchar.h> #include <stdint.h> -#include <d3d11.h> -#include <d3d11_1.h> +#include <d3d12.h> +#include "d3dx12.h" +#include <DXGI1_4.h> #include <d3dcompiler.h> #include <DirectXMath.h> #include <DirectXPackedVector.h> #include <DirectXColors.h> +#include <wrl/client.h> + +#include <string> +#include <exception> +
去掉了DX11的头文件,加入DX12的头文件。
从DirectX 10开始,微软导入了DXGI的概念。【*3】
DXGI与DX的关系有点类似OpenGL的Loader与OpenGL的关系;前者是创建绘图的上下文,后者是进行实际的绘图。
d3dx12.h 是一个工具头文件。它并不是DX12的一部分。微软是通过GitHub来提供这个文件的。这个文件主要是为了方便我们使用DX12,让代码看起来简洁一些。我在article_15的branch里面也提供了这个文件的拷贝。
#include语句当中文件名两边是<>还是””的秘密是:如果是系统或者sdk的头文件,就是<>;如果是放在我们项目当中的文件,就是””。这虽然是一些细节,但是不正确使用有的时候是会出现一些奇奇怪怪的问题。
下面的wrl/client.h是WRL的一部分。WRL是Windows Runtime Library的简称。这也是一个辅助性质的库,提供了一些符合design pattern的模板。我们这里主要是使用一个名为ComPtr的模板,用来比较智能地管理COM的Interface。
没错,又是COM。其实前面介绍的OpenGL那种运行时查询并绑定API入口的方式就和COM颇为类似。COM的中心思想就是每个模块都有一个众所周知的接口:IUnknown。这个接口支持一个Qurey方法,来查找其它的接口。这样就可以实现运行时的入口查找和调用。
接下来是一个C++异常陷阱。COM规范规定,所有的COM调用的返回值都是一个HRESULT类型的值,用来报告调用是否成功。我们前面的代码是在每次调用后检查这个返回值,如果失败进行相关log输出之后返回或者中断执行。这种写法的好处是代码可移植性高,缺点是代码里面插入了很多和原本要做的事情无关的代码,简洁性变差。我们这里参考微软官方的例子采用抛c++异常的方式处理这个返回值检查。但是需要注意的是c++异常的可移植性是不太好的。不过这里的代码本来就是平台专用代码,再加上是我们的支线任务,主要是用来打怪升级并探地图的,所以我们就这么用。
然后是全局变量的定义。我们现在因为是在探路,采用最为直观的“平的”代码方式,就是基本上是C的写法,不进行类的封装。等我们确定图形模块的划分之后,这些变量大部分都是要放到类里面去的。而另外一些则作为启动参数允许配置:如分辨率,色深等。
// global declarations
+const uint32_t nFrameCount = 2;
+const bool bUseWarpDevice = true;
+D3D12_VIEWPORT g_ViewPort = {0.0f, 0.0f,
+ static_cast(nScreenWidth),
+ static_cast(nScreenHeight)}; // viewport structure
+D3D12_RECT g_ScissorRect = {0, 0,
+
-IDXGISwapChain *g_pSwapchain = nullptr; // the pointer to the swap chain interface
+ComPtr<IDXGISwapChain3> g_pSwapChain = nullptr; // the pointer to the swap chain interface
-ID3D11Device *g_pDev = nullptr;
// the pointer to our Direct3D device interface
+ComPtr<ID3D12Device> g_pDev = nullptr; // the pointer to our Direct3D device interface
-ID3D11DeviceContext *g_pDevcon = nullptr; // the pointer to our Direct3D device context
-
-ID3D11RenderTargetView *g_pRTView = nullptr;
+ComPtr<ID3D12Resource> g_pRenderTargets[nFrameCount]; // the pointer to rendering buffer. [descriptor]
+uint32_t g_nRtvDescriptorSize;
-ID3D11InputLayout *g_pLayout = nullptr; // the pointer to the input layout
-ID3D11VertexShader *g_pVS = nullptr; // the pointer to the vertex shader
-ID3D11PixelShader *g_pPS = nullptr; // the pointer to the pixel shader
+ComPtr<ID3D12CommandAllocator> g_pCommandAllocator; // the pointer to command buffer allocator
+ComPtr<ID3D12CommandQueue> g_pCommandQueue; // the pointer to command queue
+ComPtr<ID3D12RootSignature> g_pRootSignature; // a graphics root signature defines what resources are bound to the pipeline
+ComPtr<ID3D12DescriptorHeap> g_pRtvHeap; // an array of descriptors of GPU objects
+ComPtr<ID3D12PipelineState> g_pPipelineState; // an object maintains the state of all currently set shaders
+ // and certain fixed function state objects
+ // such as the input assembler, tesselator, rasterizer and output manager
+ComPtr<ID3D12GraphicsCommandList> g_pCommandList; // a list to store GPU commands, which will be submitted to GPU to execute when done
+
+
-ID3D11Buffer *g_pVBuffer = nullptr; // Vertex Buffer
+ComPtr<ID3D12Resource> g_pVertexBuffer; // the pointer to the vertex buffer
+D3D12_VERTEX_BUFFER_VIEW g_VertexBufferView; // a view of the vertex buffer
可以看到最大的变化是DX12将GPU的命令队列暴露了出来,并且不再自动进行CPU与GPU之间的同步。
之前的DX版本对我们来说只有CPU一条时间线,所有API调用看起来是同步的。然而在DX12当中,现在多了一条GPU的时间线,大部分绘图API也从同步变成了“录制”,即,仅仅是在GPU命令队列当中生成一些指令。这个包含了指令的队列,什么时候提交给GPU进行处理,需要我们自己进行控制。所以多了好几个用于同步CPU和GPU的变量:
+// Synchronization objects +uint32_t g_nFrameIndex; +HANDLE g_hFenceEvent; +ComPtr<ID3D12Fence> g_pFence; +uint32_t g_nFenceValue;
另外一个变化就是在创建和管理方面,不再细分GPU在执行绘图指令的时候会参照的各种资源,比如顶点缓冲区,RenderingTarget等。这些统统交由一个成为Resource的接口去处理。这是因为我们已经很接近显卡驱动了。在那么低的层面,这些东西统统是buffer,没啥太大区别。
在Shader的加载方面,变化不大。但是作为演示,本次我们采用运行时编译的方式。
Layout的概念取消掉了,取而代之的是Pilpeline State Object,用以将GPU的各个功能模块串联起来形成一个渲染流水线。
因为绘图API变成了异步录制执行方式,我们需要确保这些资源在gpu实际完成绘图之前可用。在这个例子当中,我们强制GPU首先完成这些指令的执行,并通过Fence实现和GPU的同步。
// this is the function that loads and prepares the shaders
void InitPipeline() {
- // load and compile the two shaders
- ID3DBlob *VS, *PS;
-
- D3DReadFileToBlob(L"copy.vso", &VS);
- D3DReadFileToBlob(L"copy.pso", &PS);
-
- // encapsulate both shaders into shader objects
- g_pDev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &g_pVS);
- g_pDev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &g_pPS);
+ ThrowIfFailed(g_pDev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_pCommandAllocator)));
+
+ // create an empty root signature
+ CD3DX12_ROOT_SIGNATURE_DESC rsd;
+ rsd.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
+
+ ComPtr<ID3DBlob> signature;
+ ComPtr<ID3DBlob> error;
+ ThrowIfFailed(D3D12SerializeRootSignature(&rsd, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
+ ThrowIfFailed(g_pDev->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&g_pRootSignature)));
+
+ // load the shaders
+#if defined(_DEBUG)
+ // Enable better shader debugging with the graphics debugging tools.
+ UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
+#else
+ UINT compileFlags = 0;
+#endif
+ ComPtr<ID3DBlob> vertexShader;
+ ComPtr<ID3DBlob> pixelShader;
+
+ D3DCompileFromFile(
+ GetAssetFullPath(L"copy.vs").c_str(),
+ nullptr,
+ D3D_COMPILE_STANDARD_FILE_INCLUDE,
+ "main",
+ "vs_5_0",
+ compileFlags,
+ 0,
+ &vertexShader,
+ &error);
+ if (error) { OutputDebugString((LPCTSTR)error->GetBufferPointer()); error->Release(); throw std::exception(); }
+
+ D3DCompileFromFile(
+ GetAssetFullPath(L"copy.ps").c_str(),
+ nullptr,
+ D3D_COMPILE_STANDARD_FILE_INCLUDE,
+ "main",
+ "ps_5_0",
+ compileFlags,
+ 0,
+ &pixelShader,
+ &error);
+ if (error) { OutputDebugString((LPCTSTR)error->GetBufferPointer()); error->Release(); throw std::exception(); }
- // set the shader objects
- g_pDevcon->VSSetShader(g_pVS, 0, 0);
- g_pDevcon->PSSetShader(g_pPS, 0, 0);
// create the input layout object
- D3D11_INPUT_ELEMENT_DESC ied[] =
+ D3D12_INPUT_ELEMENT_DESC ied[] =
{
- {"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},
+ {"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_pDev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &g_pLayout);
- g_pDevcon->IASetInputLayout(g_pLayout);
-
- VS->Release();
- PS->Release();
+ // describe and create the graphics pipeline state object (PSO)
+ D3D12_GRAPHICS_PIPELINE_STATE_DESC psod = {};
+ psod.InputLayout = { ied, _countof(ied) };
+ psod.pRootSignature = g_pRootSignature.Get();
+ psod.VS = { reinterpret_cast<UINT8*>(vertexShader->GetBufferPointer()), vertexShader->GetBufferSize() };
+ psod.PS = { reinterpret_cast<UINT8*>(pixelShader->GetBufferPointer()), pixelShader->GetBufferSize() };
+ psod.RasterizerState= CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
+ psod.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
+ psod.DepthStencilState.DepthEnable = FALSE;
+ psod.DepthStencilState.StencilEnable= FALSE;
+ psod.SampleMask = UINT_MAX;
+ psod.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
+ psod.NumRenderTargets = 1;
+ psod.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
+ psod.SampleDesc.Count = 1;
+ ThrowIfFailed(g_pDev->CreateGraphicsPipelineState(&psod, IID_PPV_ARGS(&g_pPipelineState)));
+
+ ThrowIfFailed(g_pDev->CreateCommandList(0,
+ D3D12_COMMAND_LIST_TYPE_DIRECT,
+ g_pCommandAllocator.Get(),
+ g_pPipelineState.Get(),
+ IID_PPV_ARGS(&g_pCommandList)));
+
+ ThrowIfFailed(g_pCommandList->Close());
}
// this is the function that creates the shape to render
@@ -116,31 +257,127 @@ void InitGraphics() {
{XMFLOAT3(-0.45f, -0.5f, 0.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f)}
};
- // create the vertex buffer
- D3D11_BUFFER_DESC bd;
- ZeroMemory(&bd, sizeof(bd));
- bd.Usage = D3D11_USAGE_DYNAMIC; // write access access by CPU and GPU
- bd.ByteWidth = sizeof(VERTEX) * 3; // size is the VERTEX struct * 3
- bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // use as a vertex buffer
- bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // allow CPU to write in buffer
- g_pDev->CreateBuffer(&bd, NULL, &g_pVBuffer); // create the buffer
+ const UINT vertexBufferSize = sizeof(OurVertices);
+
+ // 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.
+ ThrowIfFailed(g_pDev->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_pVertexBuffer)));
+
- // copy the vertices into the buffer
- D3D11_MAPPED_SUBRESOURCE ms;
- g_pDevcon->Map(g_pVBuffer, NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms); // map the buffer
- memcpy(ms.pData, OurVertices, sizeof(VERTEX) * 3); // copy the data
- g_pDevcon->Unmap(g_pVBuffer, NULL); // unmap the buffer
+ // copy the vertices into the buffer
+ uint8_t *pVertexDataBegin;
+ CD3DX12_RANGE readRange(0, 0); // we do not intend to read this buffer on CPU
+ ThrowIfFailed(g_pVertexBuffer->Map(0, &readRange,
+ reinterpret_cast<void**>(&pVertexDataBegin))); // map the buffer
+ memcpy(pVertexDataBegin, OurVertices, vertexBufferSize); // copy the data
+ g_pVertexBuffer->Unmap(0, nullptr); // unmap the buffer
+
+ // initialize the vertex buffer view
+ g_VertexBufferView.BufferLocation = g_pVertexBuffer->GetGPUVirtualAddress();
+ g_VertexBufferView.StrideInBytes = sizeof(VERTEX);
+ g_VertexBufferView.SizeInBytes = vertexBufferSize;
+
+ // create synchronization objects and wait until assets have been uploaded to the GPU
+ ThrowIfFailed(g_pDev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_pFence)));
+ g_nFenceValue = 1;
+
+ // create an event handle to use for frame synchronization
+ g_hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ if (g_hFenceEvent == nullptr)
+ {
+ ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
+ }
+ // 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();
+}
所谓Fence,就是内存上一个变量。这个变量GPU和CPU都可以读写,而且通过cache控制的方法避免GPU和CPU之间出现对于这个值的内容不同步的情况。我们知道,在当代计算机系统结构当中,无论是CPU还是GPU都是有很复杂的cache结构,这种结构往往会导致CPU/GPU看到的变量的值与实际内存上保存的值不一致。
https://en.m.wikipedia.org/wiki/Cache_(computing)
而这个Fence,就是一个保证不会出现这种情况的变量。GPU在完成图形渲染任务之后,会更新这个Fence的值。而CPU在检测到这个值被更新之后,就知道GPU已经完成渲染,可以释放/重用相关资源了。
下面是构建整个swapchain。
// this function prepare graphic resources for use
-HRESULT CreateGraphicsResources(HWND hWnd)
+void CreateGraphicsResources(HWND hWnd)
+{
+ if (g_pSwapChain.Get() == nullptr)
+ {
+#if defined(_DEBUG)
+ // Enable the D3D12 debug layer.
+ {
+ ComPtr<ID3D12Debug> debugController;
+ if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
- HRESULT hr = S_OK;
- if (g_pSwapchain == nullptr)
+ debugController->EnableDebugLayer();
+ }
+ }
+#endif
+
+ ComPtr<IDXGIFactory4> factory;
+ ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&factory)));
+
+ if (bUseWarpDevice)
+ {
+ ComPtr<IDXGIAdapter> warpAdapter;
+ ThrowIfFailed(factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter)));
+
+ ThrowIfFailed(D3D12CreateDevice(
+ warpAdapter.Get(),
+ D3D_FEATURE_LEVEL_11_0,
+ IID_PPV_ARGS(&g_pDev)
+ ));
+ }
+ else
{
+ ComPtr<IDXGIAdapter1> hardwareAdapter;
+ GetHardwareAdapter(factory.Get(), &hardwareAdapter);
+
+ ThrowIfFailed(D3D12CreateDevice(
+ hardwareAdapter.Get(),
+ D3D_FEATURE_LEVEL_11_0,
+ IID_PPV_ARGS(&g_pDev)
+ ));
+ }
+
+ // 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;
+
+ ThrowIfFailed(g_pDev->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&g_pCommandQueue)));
+
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
@@ -148,103 +385,109 @@ HRESULT CreateGraphicsResources(HWND hWnd)
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
- scd.BufferCount = 1; // one back buffer
- scd.BufferDesc.Width = SCREEN_WIDTH;
- scd.BufferDesc.Height = SCREEN_HEIGHT;
+ scd.BufferCount = nFrameCount; // back buffer count
+ scd.BufferDesc.Width = nScreenWidth;
+ scd.BufferDesc.Height = nScreenHeight;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferDesc.RefreshRate.Numerator = 60;
scd.BufferDesc.RefreshRate.Denominator = 1;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
+ scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // DXGI_SWAP_EFFECT_FLIP_DISCARD only supported after Win10
+ // use DXGI_SWAP_EFFECT_DISCARD on platforms early than Win10
scd.OutputWindow = hWnd; // the window to be used
- scd.SampleDesc.Count = 4; // how many multisamples
+ scd.SampleDesc.Count = 1; // multi-samples can not be used when in SwapEffect sets to
+ // DXGI_SWAP_EFFECT_FLOP_DISCARD
scd.Windowed = TRUE; // windowed/full-screen mode
- scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen switching
+ scd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; // allow full-screen transition
- const D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL_10_1,
- D3D_FEATURE_LEVEL_10_0,
- D3D_FEATURE_LEVEL_9_3,
- D3D_FEATURE_LEVEL_9_2,
- D3D_FEATURE_LEVEL_9_1};
- D3D_FEATURE_LEVEL FeatureLevelSupported;
-
- HRESULT hr = S_OK;
-
- // create a device, device context and swap chain using the information in the scd struct
- hr = D3D11CreateDeviceAndSwapChain(NULL,
- D3D_DRIVER_TYPE_HARDWARE,
- NULL,
- 0,
- FeatureLevels,
- _countof(FeatureLevels),
- D3D11_SDK_VERSION,
- &scd,
- &g_pSwapchain,
- &g_pDev,
- &FeatureLevelSupported,
- &g_pDevcon);
-
- if (hr == E_INVALIDARG) {
- hr = D3D11CreateDeviceAndSwapChain(NULL,
- D3D_DRIVER_TYPE_HARDWARE,
- NULL,
- 0,
- &FeatureLevelSupported,
- 1,
- D3D11_SDK_VERSION,
+ ComPtr<IDXGISwapChain> swapChain;
+ ThrowIfFailed(factory->CreateSwapChain(
+ g_pCommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it
&scd,
- &g_pSwapchain,
- &g_pDev,
- NULL,
- &g_pDevcon);
- }
+ &swapChain
+ ));
+
+ ThrowIfFailed(swapChain.As(&g_pSwapChain));
- if (hr == S_OK) {
+ g_nFrameIndex = g_pSwapChain->GetCurrentBackBufferIndex();
CreateRenderTarget();
- SetViewPort();
InitPipeline();
InitGraphics();
}
}
- return hr;
-}
因为采用了ComPtr智能指针,不需要手动release了。(会在相关变量被重用或者程序结束的时候自动release)
void DiscardGraphicsResources()
{
- SafeRelease(&g_pLayout);
- SafeRelease(&g_pVS);
- SafeRelease(&g_pPS);
- SafeRelease(&g_pVBuffer);
- SafeRelease(&g_pSwapchain);
- SafeRelease(&g_pRTView);
- SafeRelease(&g_pDev);
- SafeRelease(&g_pDevcon);
+ WaitForPreviousFrame();
+
+ CloseHandle(g_hFenceEvent);
}
下面是录制绘图指令到command list当中。
+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.
+ ThrowIfFailed(g_pCommandAllocator->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.
+ ThrowIfFailed(g_pCommandList->Reset(g_pCommandAllocator.Get(), g_pPipelineState.Get()));
+
+ // Set necessary state.
+ g_pCommandList->SetGraphicsRootSignature(g_pRootSignature.Get());
+ g_pCommandList->RSSetViewports(1, &g_ViewPort);
+ g_pCommandList->RSSetScissorRects(1, &g_ScissorRect);
+
+ // Indicate that the back buffer will be used as a render target.
+ g_pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
+ g_pRenderTargets[g_nFrameIndex].Get(),
+ D3D12_RESOURCE_STATE_PRESENT,
+ D3D12_RESOURCE_STATE_RENDER_TARGET));
+
+ CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_pRtvHeap->GetCPUDescriptorHandleForHeapStart(), g_nFrameIndex, g_nRtvDescriptorSize);
+ g_pCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
+
// clear the back buffer to a deep blue
const FLOAT clearColor[] = {0.0f, 0.2f, 0.4f, 1.0f};
- g_pDevcon->ClearRenderTargetView(g_pRTView, clearColor);
+ g_pCommandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
// do 3D rendering on the back buffer here
{
// select which vertex buffer to display
- UINT stride = sizeof(VERTEX);
- UINT offset = 0;
- g_pDevcon->IASetVertexBuffers(0, 1, &g_pVBuffer, &stride, &offset);
+ g_pCommandList->IASetVertexBuffers(0, 1, &g_VertexBufferView);
// select which primtive type we are using
- g_pDevcon->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+ g_pCommandList->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// draw the vertex buffer to the back buffer
- g_pDevcon->Draw(3, 0);
+ g_pCommandList->DrawInstanced(3, 1, 0, 0);
+ }
+
+ // Indicate that the back buffer will now be used to present.
+ g_pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
+ g_pRenderTargets[g_nFrameIndex].Get(),
+ D3D12_RESOURCE_STATE_RENDER_TARGET,
+ D3D12_RESOURCE_STATE_PRESENT));
+
+ ThrowIfFailed(g_pCommandList->Close());
}
提交上面录制的绘图指令(command list),并执行frame buffer交换,输出画面。
// this is the function used to render a single frame
void RenderFrame()
{
+ // record all the commands we need to render the scene into the command list
+ PopulateCommandList();
+
+ // execute the command list
+ ID3D12CommandList *ppCommandLists[] = { g_pCommandList.Get() };
+ g_pCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
+
// swap the back buffer and the front buffer
- g_pSwapchain->Present(0, 0);
+ ThrowIfFailed(g_pSwapChain->Present(1, 0));
+
+ WaitForPreviousFrame();
}
绘图指令由Draw升级为了DrawInstanced。后者支持一次drawcall绘制一组相同的物体。这个功能在绘制诸如树木、花草、篱笆、地面、吃瓜群众等群体环境物体时十分有效,可以减少很多CPU-GPU之间的同步成本。在VR当中,同一个物体会出现在两个眼睛的视野当中,也是采用这种方式减少drawcall的。
编译命令行如下(Visual Studio工具链):
D:wenliSourceReposGameEngineFromScratchPlatformWindows>cl /EHsc helloengine_d3d12.cpp user32.lib d3d12.lib dxgi.lib d3dcompiler.lib
这篇代码因为用了很多微软的东西,暂时无法用clang(包括clang-cl)编译。不过反正本来就是windows平台独有的DX12,这没关系。
好了。这样我们就完成了DX11到DX12的升级。下一篇将会加入贴图。
(– EOF –)
参考资料:
- Tutorial: Migrating Your Apps to DirectX* 12 – Part 1
- Creating a basic Direct3D 12 component
- DXGI
- DirectXMath Programming Guide

本作品采用知识共享署名 4.0 国际许可协议进行许可。











