LoginSignup
2
0

More than 3 years have passed since last update.

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

Posted at

背景

  • 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?

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 が見つからないエラーになります.

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

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

MinGW 向け修正

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

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

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 機械学習若人さまへと昇華なされるスキームを確立すrたびに出たい

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0