llvm-mingw(clang) で D3D12, DirectML コードをコンパイルするメモ

## 背景

* Windows, WSL2 で DirectML で機械学習したい.
* しかし MSVC 環境で開発めんどい. MinGW + Linux 環境で開発したい

llvm-mingw(clang) でいけます.

## 環境

* llvm-mingw 20200325(LLVM 10.0)
* Ubuntu 18.04 or 20.04
  * WSL 環境の Ubuntu だと, `.exe` を Windows プロセスとして直実行できてよい  
* directml.h などのヘッダ
  * Windows SDK などから取得
  

## ComPtr?

https://github.com/Microsoft/DirectXTK/wiki/ComPtr

DirectX(COM API?)では, ComPtr(スマートポインタ) 使ってリソース管理するのがサンプルコードでよくあります.

今のナウでヤングな時代, llvm-mingw(clang)なら, asan(address sanitizer) が使えメモリリーク検出しやすいですから無理して ComPtr(スマートポインタ)使わなくても素の手動管理でもいいような気がしますので, とりあえずは ComPtr 使わずコード書きます.

内部のリソース扱いがちょっと違うので, `std::shared_ptr` ととは直接は使えません.

参考までに, ComPtr 関連はいくつか実装あってややこしいです.

* cppwinrt/winrt/Windows.Foundation.h の com_ptr.
  * DirectML のサンプルで使われているやつ.
  * C++/WinRT 用(ナウでヤングな C++ プロジェクト用?)
  * MinGW にはありません
  * ちなみにこの cppwinrt C++ヘッダ自体は MIT ライセンスなので, 頑張れば com_ptr 部分だけ抜き出して MinGW に使えるかも?
  * 実行には api-ms-win-crt-****.dll が必要
* wrl/client.h
  * ComPtr 
  * Legacy(?) C++ 向け
  * MinGW に定義**ある** が, 実行時 dll が必要でめんどい.
* CComPtr
  * ATL 依存(MSVC ヘッダ?)
  * MinGW に定義は無い
  
したがって MinGW 環境で標準で使えるのは `wrl/client.h` の ComPtr です.
ただ, これの実行には api-ms-win-crt-***.dll が必要(ucrt にしたら不要?)でめんどいです.

ComPtr 実装自体はそんなに大きくないので, 必要なら自前実装がよいでしょう.

## d3d12.h header and IDL

directml, d3d12 は, IDL(インターフェース定義)から, `.h` を作っています.
MSVC 環境では MIDL でこの処理をしているはずです.

WINE で widl が用意されています. しかしなぜか d3d12.idl は扱えませんでした(パースエラー). d3d12.idl が import している ibl をうまく MinGW から引っ張ってくればいけるかもしれません. が調べるの面倒なので辞めました.

vkd3d(WINE D3D12 Vulkan 実装)のビルドメモ
https://qiita.com/syoyo/items/c9177ce266cfec713d96

とりあえず Windows SDK の d3d12.h など使えますが, 後述する UUID 定義が必要です.
vkd3d に d3d12.h 定義がありますので, こちらを利用するのも手でしょう(こちらの場合は UUID 別途定義不要).

## d3d12.so

`.dll` から作ったものらしき .so(import lib)がすでに llvm-mingw パッケージにありました.

### uuid

Windows SDK の .h を使うと, __uuidof(x) で D3dD12 クラスの uuid が見つからないエラーになります.

https://github.com/HenrikBengtsson/affxparser/issues/25

https://github.com/bkaradzic/bgfx/blob/master/src/renderer_d3d12.h

MinGW では, `__mingw__uuidof` を呼び出しています.

`__CRT_UUID_DECL` で UUID 定義するか, bgfx のように template に定義を与えます.
(idl から自動で uuid 抜き出して変換が理想ですが, クラス数少なければ手書きで変換でもいいかも)

### MinGW 向け修正

https://github.com/bkaradzic/bgfx/pull/1239

`d3d12sdklayers.h` で, static_assert での定数ルックアップがうまくいきません.

https://git.m4xw.net/Switch/RetroArch/RetroArch/commit/0e363164b1171e942ca5c815ec5bd8a79ca00744

とりあえずコメントアウトします.

## directml.so

MinGW パッケージにはありません. .dll から import lib を作ってもいいですが, DirectML は関数 2 個しかないので, .def 手書きで作って import lib 作るのでもよさそうです.
もしくは, wglGetProcAddress のように, 実行時 symbol lookup するか.
また, llvm-mingw の場合は .lib を直接リンク(`-ldirectml`)できます.


## コード例

```
#include <rpc.h>
#include <rpcndr.h>

#include <cstdio>

//#define __in
//#define __out
//#define __in_opt
//#define __inout
//#define __in_bcount(x)
//#define __in_ecount(x)
//#define __out_ecount(x)
//#define __out_bcount(x)
#define __out_ecount_part_opt(x, y)
#define __out_bcount_opt(x)
#define __in_bcount_opt(x)
#define __in_ecount_opt(x)
#define __out_ecount_opt(x)
//#define __out_opt
//#define __inout_opt

#include <dxgi1_4.h>

#include "d3d12.h"
#include "directml.h"

#define CHECK_HRESULT(result)                                             \
  {                                                                       \
    if (result != S_OK) {                                                 \
      fprintf(stderr, "%s:%d failed: %ld\n", __FILE__, __LINE__, result); \
      exit(-1);                                                           \
    }                                                                     \
  }

// https://github.com/bkaradzic/bgfx/blob/master/src/renderer_d3d12.h
#if defined(__MINGW32__)  // BK - temp workaround for MinGW until I nuke d3dx12
                          // usage.
// namespace {
#if 0
  //__extension__ template<typename Ty>
  //const GUID& __mingw_uuidof();

  template<>
  const GUID& __mingw_uuidof<ID3D12Device*>()
  {
    //MIDL_INTERFACE("189819f1-1db6-4b57-be54-1821339b85f7")
    static const GUID IID_ID3D12Device0 = { 0x189819f1, 0x1db6, 0x4b57, { 0xbe, 0x54, 0x18, 0x21, 0x33, 0x9b, 0x85, 0xf7 } };
    return IID_ID3D12Device0;
  }

  template<>
  const GUID& __mingw_uuidof<ID3D12CommandAllocator*>()
  {
    static const GUID IID_ID3D12CommandAllocator0 = { 0x6102dee4, 0xaf59, 0x4b09, { 0xb9, 0x99, 0xb4, 0x4d, 0x73, 0xf0, 0x9b, 0x24 } };

    return IID_ID3D12CommandAllocator0;
  }
//}
#else

#include "d3d12-uuids.h"
#include "directml-uuids.h"

#endif

#endif  // defined(__MINGW32__)

inline UINT64 DMLCalcBufferTensorSize(DML_TENSOR_DATA_TYPE dataType,
                                      UINT dimensionCount,
                                      _In_reads_(dimensionCount)
                                          const UINT *sizes,
                                      _In_reads_opt_(dimensionCount)
                                          const UINT *strides) {
  UINT elementSizeInBytes = 0;
  switch (dataType) {
    case DML_TENSOR_DATA_TYPE_FLOAT32:
    case DML_TENSOR_DATA_TYPE_UINT32:
    case DML_TENSOR_DATA_TYPE_INT32:
      elementSizeInBytes = 4;
      break;

    case DML_TENSOR_DATA_TYPE_FLOAT16:
    case DML_TENSOR_DATA_TYPE_UINT16:
    case DML_TENSOR_DATA_TYPE_INT16:
      elementSizeInBytes = 2;
      break;

    case DML_TENSOR_DATA_TYPE_UINT8:
    case DML_TENSOR_DATA_TYPE_INT8:
      elementSizeInBytes = 1;
      break;

    default:
      return 0;  // Invalid data type
  }

  UINT64 minimumImpliedSizeInBytes = 0;
  if (!strides) {
    minimumImpliedSizeInBytes = sizes[0];
    for (UINT i = 1; i < dimensionCount; ++i) {
      minimumImpliedSizeInBytes *= sizes[i];
    }
    minimumImpliedSizeInBytes *= elementSizeInBytes;
  } else {
    UINT indexOfLastElement = 0;
    for (UINT i = 0; i < dimensionCount; ++i) {
      indexOfLastElement += (sizes[i] - 1) * strides[i];
    }

    minimumImpliedSizeInBytes = (indexOfLastElement + 1) * elementSizeInBytes;
  }

  // Round up to the nearest 4 bytes.
  minimumImpliedSizeInBytes = (minimumImpliedSizeInBytes + 3) & ~3;

  return minimumImpliedSizeInBytes;
}

int main(int argc, char **argv) {

  IDXGIFactory4 *dxgiFactory{};
  CHECK_HRESULT(CreateDXGIFactory1(__uuidof(dxgiFactory),
                                   reinterpret_cast<void **>(&dxgiFactory)));

  IDXGIAdapter *dxgiAdapter{};
  UINT adapterIndex{};
  HRESULT hr{};

  ID3D12Device *d3D12Device{};

  do {
    dxgiAdapter = nullptr;
    CHECK_HRESULT(dxgiFactory->EnumAdapters(adapterIndex, &dxgiAdapter));
    ++adapterIndex;

    hr = ::D3D12CreateDevice(dxgiAdapter, D3D_FEATURE_LEVEL_11_0,
                             __uuidof(d3D12Device),
                             reinterpret_cast<void **>(&d3D12Device));
    printf("hr = %ld\n", hr);
    if (hr == DXGI_ERROR_UNSUPPORTED) continue;
    CHECK_HRESULT(hr);
  } while (hr != S_OK);

  printf("got it\n");

  D3D12_COMMAND_QUEUE_DESC commandQueueDesc{};
  commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
  commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;

  ID3D12CommandQueue *commandQueue{};
  ID3D12CommandAllocator *commandAllocator{};
  ID3D12GraphicsCommandList *commandList{};

  CHECK_HRESULT(d3D12Device->CreateCommandQueue(
      &commandQueueDesc, __uuidof(commandQueue),
      reinterpret_cast<void **>(&commandQueue)));

  CHECK_HRESULT(d3D12Device->CreateCommandAllocator(
      D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(*commandAllocator),
      reinterpret_cast<void **>(&commandAllocator)));

  CHECK_HRESULT(d3D12Device->CreateCommandList(
      0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, nullptr,
      __uuidof(*commandList), reinterpret_cast<void **>(&commandList)));

  DML_CREATE_DEVICE_FLAGS dmlCreateDeviceFlags = DML_CREATE_DEVICE_FLAG_NONE;

#if defined(_DEBUG)
  // If the project is in a debug build, then enable debugging via DirectML
  // debug layers with this flag.
  dmlCreateDeviceFlags |= DML_CREATE_DEVICE_FLAG_DEBUG;
#endif

  IDMLDevice *dmlDevice{};
  CHECK_HRESULT(DMLCreateDevice(d3D12Device, dmlCreateDeviceFlags,
                                __uuidof(dmlDevice),
                                reinterpret_cast<void **>(&dmlDevice)));

  printf("directml got it\n");

  dmlDevice->Release();

  d3D12Device->Release();
  dxgiFactory->Release();

  return 0;
}
```

uuids.h は, 以下のような定義をしています.

```
__CRT_UUID_DECL(IDMLDevice, 0x6dbd6437, 0x96fd, 0x423f, 0xa9, 0x8c, 0xae, 0x5e, 0x7c, 0x2a, 0x57, 0x3f);
```


Makefile は以下のようになります.

```
CXX=/home/syoyo/local/llvm-mingw-20200325-ubuntu-18.04/bin/x86_64-w64-mingw32-clang++
EXTRA_CXXFLAGG=-I. -DD3D12_IGNORE_SDK_LAYERS

all:
        $(CXX) $(EXTRA_CXXFLAGG) -std=c++11 main.cc -ldxgi -ld3d12 -ldirecml 
```

これで, できた a.exe を Windows で実行するとうごくはずです!


## TODO

* [ ] D3D12 debug layer を有効にする
* [ ] 一部 D3D12 API の gcc/clang 向け ABI 修正をする https://github.com/bkaradzic/bgfx/pull/1239 
* [ ] DirectML の自前実装(e.g. Vulkan で実装)と, vkd3d を組み合わせ, Linux native でも動くようにする. 
* [ ] 優秀な GPU 機械学習若人さまが, **llvm-mingw + DirectML で人類史上最速で優秀な WSL2 + DirectML + GPU 機械学習若人さまへと昇華なされるスキーム**を確立すｒたびに出たい

## 参考文献

* MinGWからDirect3D11を呼び出す http://tsubaki.hatenablog.com/entry/20110115/1295080728