DirectX12 その１　三角形表示 

## はじめに
DirectX12完全に理解したいとおもったんでDirectX12始めました。

## 実行画面　　
![directx12_triangle.png](https://qiita-image-store.s3.amazonaws.com/0/62873/7df80229-2fa9-e455-5146-bc962205fba0.png)

## プログラムの流れ  

- 初期化
- 繰り返す
    - 更新
    - 描画
- 破棄

### 初期化  
#### Windowを作成する  
省略します。

####パイプラインを初期化する
- デバッグレイヤーを有効にする
  
```cpp 
    UINT dxgiFactoryFlags = 0;
    ComPtr<ID3D12Debug> debugController;
    if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
    {
        debugController->EnableDebugLayer();
    }
```
  
D3D12GetDebugInterfaceでデバッグレイヤーを取得してEnableDebugLayer()でデバッグレイヤーを有効化する。
このプログラムではスマートポインタ使ってるのでRelease()を呼ぶ必要ないです
- デバイスを作成する  

 ``` cpp  
    ComPtr<IDXGIFactory4> factory;
    CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));
    if(m_useWarpDevice)
    {
            ComPtr<IDXGIAdapter> warpAdapter;
            factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter));
            D3D12CreateDevice(
                warpAdapter.Get(),
                D3D_FEATURE_LEVEL_11_0,
                IID_PPV_ARGS(&m_device));
    }
    else
    {
        ComPtr<IDXGIAdapter1> hardwareAdapter;
        GetHardwareAdapter(factory.Get(), &hardwareAdapter);
        D3D12CreateDevice(
            hardwareAdapter.Get(),
            D3D_FEATURE_LEVEL_11_0,
            IID_PPV_ARGS(&m_device));
    }
```
      
D3D12CreateDevice でデバイスを作成します。
  
- コマンドキューを作成する  

```cpp 
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

    m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
``` 

コマンドキューとは実行の待ち行列です。
ID3D12CommandQueue::ExecuteCommandListsを呼び出して、指定した順にGPUが実行すべき命令のリスト（CommandList）を実行します。  
D3D12_COMMAND_LIST_TYPEでは送信するコマンドリストのタイプを指定します。
D3D12_COMMAND_LIST_TYPE_DIRECT　汎用。
D3D12_COMMAND_LIST_TYPE_COMPUTE 計算用に使用。
D3D12_COMMAND_LIST_TYPE_COPY コピーコマンドのみ実行可能。  

- スワップチェインを作成する
      
```cpp
    DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
    swapChainDesc.BufferCount = FrameCount;
    swapChainDesc.BufferDesc.Width = m_width;
    swapChainDesc.BufferDesc.Height = m_height;
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapChainDesc.OutputWindow = m_hwnd;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.Windowed = TRUE;

    ComPtr<IDXGISwapChain> swapChain;
    factory->CreateSwapChain(m_commandQueue.Get(),
            &swapChainDesc,
            &swapChain);

    swapChain.As(&m_swapChain);
```

フレームバッファの描画側と表示側を切り替えるもの。
- ディスクリプタヒープを作成します。
    
```cpp
    D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
    rtvHeapDesc.NumDescriptors = FrameCount;
    rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap));
    m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
```  
 
ディスクリプタヒープからディスクリプタを格納するメモリが確保されます
ディスクリプタとはリソースパラメーターを定義したデータです。GPUメモリに確保すると同時にシステムにもミラーが作られ、CPUとGPU両方からアクセスできます
　 ディスクリプタが必要なリソースはConstantBufferView,SRV,UAV,RTV,DSV,Samplerです  
- レンダーターゲットビューを作成する  

```cpp 
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
    // フロントとバッグであわせて２つのRTVを作成  
    for (UINT n = 0; n < FrameCount; n++)
    {
        m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n]));
        m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
        rtvHandle.Offset(1, m_rtvDescriptorSize);
    }
```

- コマンドアロケーターを作成します
    
```cpp
    m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
```  
  
コマンドアロケーターは、コマンドリストに割り当てられたメモリを管理します。
コマンドアロケーターのコマンドタイプはコマンドリストのタイプと同じでなければならないのでD3D12_COMMAND_LIST_TYPE_DIRECTをつかいます。複数のコマンドリストから１つのコマンドアロケーターをバインドすることもできます。
また、コマンドアロケータによって割り当てられたメモリを再利用するためにResetを呼ぶ必要がありますが、フリースレッドではないので複数のスレッドから同じアロケータで同時に呼び出すことはできません。

#### リソースを初期化

- ルートシグネチャを作成する
    
```cpp
    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);
    m_device->CreateRootSignature(0,
                                  signature->GetBufferPointer(),
                                  signature->GetBufferSize(),
                                  IID_PPV_ARGS(&m_rootSignature));  
```  

ルートシグネチャとは、シェーダーに渡すパラメーターのフォーマットを決めるものです。
リソースとシェーダの対応付けを行います。
ルートシグネチャの定義の内容が一致していれば同じオブジェクトとしてみなされます。
シェーダーと同じようにhlslに記述することもできます。    
- シェーダーをコンパイル
      
```cpp
    ComPtr<ID3DBlob> vertexShader;
    ComPtr<ID3DBlob> pixelShader;

    #if defined(_DEBUG)
            UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
    #else
            UINT compileFlags = 0;
    #endif 
            D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr);
            D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr);
```

- インプットレイアウト 
    
```cpp
    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 }
    };
```  

頂点の情報を定義します。  

- パイプラインステートオブジェクトを作成する
    
```cpp
    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
    psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
    psoDesc.pRootSignature = m_rootSignature.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;
    m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&m_pipelineState));
```
      
パイプラインステートオブジェクトとはインプットレイアウト、シェーダ、各ステートなどをまとめたオブジェクトです。
DirectX12ではシェーダやステートを単体で設定を変えるAPIはありません。１つでも変える場合はパイプラインステートオブジェクトをもう一度つくることになります。  

- コマンドリストを作成する
    
```cpp
    m_device->CreateCommandList(0,
                                D3D12_COMMAND_LIST_TYPE_DIRECT, 
                                m_commandAllocator.Get(), 
                                m_pipelineState.Get(),
                                IID_PPV_ARGS(&m_commandList));
```

コマンドリストには描画や状態を変更する命令を記録していきます。記録された時点ではまだ実行されずExecuteCommandListsを呼び出して実行します。
- コマンドリストを閉じる
    
```cpp
    m_commandList->Close();
```

- 頂点バッファを作成する
    
```cpp
    Vertex triangleVertices[] =
    {
            { { 0.0f, 0.25f * m_aspectRatio, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
            { { 0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
            { { -0.25f, -0.25f * m_aspectRatio, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
    };

    const UINT vertexBufferSize = sizeof(triangleVertices);
    m_device->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(&m_vertexBuffer));
```  
   
ここで、三角形を作ります
- 頂点データを頂点バッファにコピー
    

```cpp
    UINT8* pVertexDataBegin;
    CD3DX12_RANGE readRange(0, 0);
    m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin));
    memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
    m_vertexBuffer->Unmap(0, nullptr);
```

- 頂点バッファビューを初期化する
    
```cpp
m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
m_vertexBufferView.StrideInBytes = sizeof(Vertex);
m_vertexBufferView.SizeInBytes = vertexBufferSize;
```
コマンドリストに登録する際に使います。  

-フェンスを作成して初期化する
    
```cpp
    m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
    m_fenceValue = 1;
```

フェンスはコマンドリストが完了したかどうかを知るために使います。コマンドリストが実行されている間はコマンドアロケーターのReset呼び出しはよくないです。ID3D12CommandAllocator::Resetを呼ぶとコマンドアロケータで記録されたコマンドを持つコマンドリストをGPUがもう実行しないと判断します。 
- フレーム同期に使用するイベントハンドルを作成する
    
```cpp
    m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
```
- GPUが終了するのを待ちます


### 更新
  必要に応じて、定数、頂点、インデックスのバッファやその他を変更します 
 
```cpp
  void Direct3D12HelloTriangle::OnUpdate()
  {
    // TODO:更新処理
  }
```  


### 描画
#### コマンドリストを入力
- コマンドリストアロケータをリセット
  
    
```cpp
    m_commandAllocator->Reset();
    m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get());
```

- グラフィックスのルートシグネチャを設定, ビューポートとシーザーを設定

```cpp
    m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
    m_commandList->RSSetViewports(1, &m_viewport);
    m_commandList->RSSetScissorRects(1, &m_scissorRect)
```

- バックバッファがレンダーターゲットとして使用されることを示すリソースバリアを設定する。
    
```cpp
    m_commandList->ResourceBarrier(1,
                                   &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
                                   D3D12_RESOURCE_STATE_PRESENT, 
                                   D3D12_RESOURCE_STATE_RENDER_TARGET));
```

- コマンドを記録します
    
```cpp
    const float clearColor[] = { 0.0f,0.2f,0.4f,1.0f };
    m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
    m_commandList->DrawInstanced(3, 1, 0, 0);
```

- 使っているバックバッファを指定する
    
```cpp
    m_commandList->ResourceBarrier(1,
                                   &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), 
                                   D3D12_RESOURCE_STATE_RENDER_TARGET,
                                   D3D12_RESOURCE_STATE_PRESENT));
```

- コマンドリストを閉じる
    
```cpp
    m_commandList->Close();
```

コマンドリストへの記録が完了したことを合わします。  

- コマンドリストを実行  
    
```cpp
    ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
    m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
```
  
- フレームを表示  
    
```cpp
    m_swapChain->Present(1, 0);
```
    
- コマンドの実行されるのを待つ。(ここに書いてあるフレームを完了するのを待つ処理は非効率。)
    
```cpp
    void Direct3D12HelloTriangle::WaitForPreviousFrame()
    {
        const UINT64 fence = m_fenceValue;
        m_commandQueue->Signal(m_fence.Get(), fence);
        m_fenceValue++;

        if (m_fence->GetCompletedValue() < fence)
        {
            m_fence->SetEventOnCompletion(fence, m_fenceEvent);
            WaitForSingleObject(m_fenceEvent, INFINITE);
        }

        m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
    }
```
    
ID3D12CommandQueue :: Signalを呼び出してフェンスを進めると、値が更新されます。フェンスの値をチェックして、次の処理を開始できるかどうかを判断できます。
### 破棄
- GPUが終了するのを待ち, イベントを閉じます。  

```cpp
void Direct3D12HelloTriangle::OnDestroy()
{
    WaitForPreviousFrame();
    CloseHandle(m_fenceEvent);
}
```

## おわり
長かった。かなり駆け足だったけど流れだけは説明しました。  
ソース：[DirectX12Triangle](https://github.com/hiemon/DirectX12TrainingCamp/tree/master/DirectX12TrainingCamp/1)