背景
- 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 プロセスとして直実行できてよい
- WSL 環境の Ubuntu だと,
- 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たびに出たい
参考文献
- MinGWからDirect3D11を呼び出す http://tsubaki.hatenablog.com/entry/20110115/1295080728