DirectX12 ComputeShaderの実装

DirectX12を使ったComputeShaderの実装を説明しているサイトがほとんどないので、
これから実装する方の参考になれば幸いです。

```c++:Compute.h
#include <d3d12.h>
class Compute
{
   public:
      Compute(ID3D12Device* dev);
      ~Compute();

      // 実行
      void Execution(ID3D12CommandQueue* queue);

   private:
      // ルートシグネチャの生成
      long CreateRoot(void);
      // パイプラインの生成
      long CreatePipe(void);
      // ヒープの生成
      long CreateHeap(void);
      // リソースの生成
      long CreateRsc(void);
      // UAVの生成
      void CreateUAV(void);
      // リソースのマップ
      long Map(void);
      // 初期化
      void Init(void);
      // コマンドの生成
      long CreateCom(void);


      // デバイス
      ID3D12Device* dev;
      // コンピュート用ルートシグネチャ
      ID3D12RootSignature* root;
      // シェーダー情報
      ID3DBlob* shader;
      // コンピュート用パイプライン
      ID3D12PipelineState* pipe;
      // ヒープ
      ID3D12DescriptorHeap* heap;
      // リソース
      ID3D12Resource* rsc;
      // 送受信用データ
      void* data;
      // コマンドアロケータ
      ID3D12CommandAllocator* allo;
      // コマンドリスト
      ID3D12GraphicsCommandList* list;
}
```
ヘッダーでの一例を記しておきます。
デバイスは作られている前提で進めていきます。

```c++:Compute.cpp
#include <d3dcompiler>
#include "Compute.h"
#include <vector>

// 確認用配列
std::vector<float>test(256, 0);

Compute::Compute(ID3D12Device* dev) : 
      dev(dev), root(nullptr), shader(nullptr), pipe(nullptr), heap(nullptr), rsc(nullptr),
      data(nullptr), allo(nullptr), list(nullptr)
{
   Init();
   CreateCom();
}
Compute::~Compute()
{
   D3D12_RANGE range{0, 1};
   rsc->Unmap(0, &range);
}
// ルートシグネチャの生成
long Compute::CreateRoot(void)
{
   //シェーダーコンパイル
   auto hr = D3DCompileFromFile(L"Test.hlsl", nullptr,
             D3D_COMPILE_STANDARD_FILE_INCLUDE, "CS", "cs_5_1", D3DCOMPILE_DEBUG | 
             D3DCOMPILE_SKIP_OPTIMIZATION, 0, &shader, nullptr);
   //シェーダーからルートシグネチャ情報を取得
   ID3DBlob* sig = nullptr;
   hr = D3DGetBlobPart(shader->GetBufferPointer(), shader->GetBufferSize(),
        D3D_BLOB_ROOT_SIGNATURE, 0, &sig);
   //ルートシグネチャの生成
   hr = dev->CreateRootSignature(0, sig->GetBufferPointer(), sig->GetBufferSize(),
                                 IID_PPV_ARGS(&root));
   return hr;
}
// パイプラインの生成
long Compute::CreatePipe(void)
{
   D3D12_COMPUTE_PIPELINE_STATE_DESC desc{};
   desc.CS.pShaderBytecode = shader->GetBufferPointer();
   desc.CS.BytecodeLength  = shader->GetBufferSize();
   desc.Flags              = D3D12_PIPELINE_STATE_FLAG_NONE;
   desc.NodeMask           = 0;
   desc.pRootSignature     = root;

   auto hr = dev->CreateComputePipelineState(&desc, IID_PPV_ARGS(&pipe));
   return hr;
}
// ヒープの生成
long Compute::CreateHeap(void)
{
   //今回はテストとしてリソースは1つとして作成
   D3D12_DESCRIPTOR_HEAP_DESC desc{};
   desc.Flags          = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
   desc.NodeMask       = 0;
   desc.NumDescriptors = 1;
   desc.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;

   auto hr = dev->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&heap));
   return hr;
}
// リソースの生成
long Compute::CreateRsc(void)
{
   D3D12_HEAP_PROPERTIES prop{};
   prop.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK;
   prop.CreationNodeMask     = 1;
   prop.MemoryPoolPreference = D3D12_MEMORY_POOL_L0;
   prop.Type                 = D3D12_HEAP_TYPE_CUSTOM;
   prop.VisibleNodeMask      = 1;
   //サイズは定数バッファと同じように指定
   D3D12_RESOURCE_DESC desc{};
   desc.Alignment        = 0;
   desc.DepthOrArraySize = 1;
   desc.Dimension        = D3D12_RESOURCE_DIMENSION_BUFFER;
   desc.Flags            = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
   desc.Format           = DXGI_FORMAT_UNKNOWN;
   desc.Height           = 1;
   desc.Layout           = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
   desc.MipLevels        = 1;
   desc.SampleDesc       = { 1, 0 };
   desc.Width            = (sizeof(float) * test.size() + 0xff)&~0xff;

   auto hr = dev->CreateCommittedResource(&prop, D3D12_HEAP_FLAG_NONE, &desc, 
                                          D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, 
                                          IID_PPV_ARGS(&rsc)));
   return hr;
}
// UAVの生成
void Compute::CreateUAV(void)
{
   //今回はGPUから受け取るのはchar型にしています
   D3D12_UNORDERED_ACCESS_VIEW_DESC desc{};
   desc.ViewDimension              = D3D12_UAV_DIMENSION_BUFFER;
   desc.Format                     = DXGI_FORMAT_UNKNOWN;
   desc.Buffer.NumElements         = test.size();
   desc.Buffer.StructureByteStride = sizeof(float);

　　dev->CreateUnorderedAccessView(dev, nullptr, &desc, heap-> GetCPUDescriptorHandleForHeapStart());
}
// リソースのマップ
long Compute::Map(void)
{
   D3D12_RANGE range{0, 1};
   auto hr = rsc->Map(0, &range, &data);
   return hr;
}
// 初期化
void Init(void)
{
   CreateRoot();
   CreatePipe();
   CreateHeap();
   CreateRsc();
   CretaeUAV();
   Map();
}
// コマンドの生成
long CreateCom(void)
{
   auto hr = dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COMPUTE,
                                         IID_PPV_ARGS(&allo));
   hr = dev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COMPUTE, allo, nullptr,
                               IID_PPV_ARGS(&list));
   return hr;
}
// 実行
void Compute::Execution(ID3D12CommandQueue* queue)
{
   //コマンドのリセット
   allo->Reset();
   list->Reset(allo, nullptr);
   //それぞれのセット
   list->SetComputeRootSignature(root);
   list->SetPipelineState(pipe);
   list->SetDescriptorHeaps(1, &h);

   auto handle = heap->GetGPUDescriptorHandleForHeapStart();
   list->SetComputeRootDescriptorTable(0, handle);

   //コンピュートシェーダーの実行(今回は256個のスレッドグループを指定)
   list->Dispatch(test.size(), 1, 1);

   list->Close();

   //コマンドの実行
   ID3D12CommandList* com[] = {
      list->GetList(),
   };
   queue->ExecuteCommandLists(1, com);

   /*
   -----ここでID3D12Fenceの待機をさせる-----
   /*

   //GPUからデータをもらう
   test.assign((float*)data, (float*)data + test.size());
}
```
cppはこのような感じです。
考え方としては定数バッファとほぼ同じ要領で扱うことができます。

```hlsl:Test.hlsl
// ルートシグネチャの宣言
#define RS "RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT),"\
                    "DescriptorTable(UAV(u0, numDescriptors = 1, space = 0, offset = DESCRIPTOR_RANGE_OFFSET_APPEND), "\
                                    "visibility = SHADER_VISIBILITY_ALL),"\
                    "StaticSampler(s0, "\
                                  "filter         = FILTER_MIN_MAG_MIP_LINEAR, "\
                                  "addressU       = TEXTURE_ADDRESS_WRAP, "\
                                  "addressV       = TEXTURE_ADDRESS_WRAP, "\
                                  "addressW       = TEXTURE_ADDRESS_WRAP, "\
                                  "mipLodBias     = 0.0f, "\
                                  "maxAnisotropy  = 0, "\
                                  "comparisonFunc = COMPARISON_NEVER, "\
                                  "borderColor    = STATIC_BORDER_COLOR_TRANSPARENT_BLACK, "\
                                  "minLOD         = 0.0f, "\
                                  "maxLOD         = 3.402823466e+38f, "\
                                  "space          = 0, "\
                                  "visibility     = SHADER_VISIBILITY_ALL)"

// 共有データ
RWStructuredBuffer<float> real : register(u0);

[RootSignature(RS)]
[numthreads(1, 1, 1)]
void CS(uint3 gID : SV_GroupID)
{
   //共有データに配列番号が入る(0~255)
   real[gID.x] = gID.x;
}
```
DirectX12でComputeShaderを実装する一例をかかせていただきました。
この実装方法では扱いやすさを重視しているので、GPUとCPUとの送受信速度は重視していないので
そこはご了承ください。