SwiftShader の Software Vulkan 実装で自前 Vulkan アプリを作る準備(Linux)

## 背景

* Vulkan を使いたい C++ アプリ(グラフィックス, 機械学習)がある.
* Vulkan(GPU) を直接叩くと, ハマったときにデバッグで時間が無限に溶けてしまいそうなので, まずはエミュレーションで C++ Vulkan アプリが動くか試したい.
* Vulkanを GPU が無い環境でも試したい(e.g. サーバーでグラフィックスや機械学習を動かしたり, シェーダの動作チェックとか)
* Vulkan アプリの開発は macOS でやりたいとか.
* GPU Vulkan でうまく動かない時に, ドライバのバグなのか, 自分のコードが悪いのかの切り分けをしたい(Vulkan validation layer や, RenderDoc などの解析ツールでもわからないような不都合や疑問に遭遇した時)
  * NVIDIA GPU だと NSight で Vulkan デバッグできますが, AMD や Intel GPU だと動かせない

ソフトウェア GLES 実装である SwiftShader には最近 Vulkan 実装がつきました.
この Vulakn 部分だけを抜き出して使ってみたいと思います.

https://github.com/google/swiftshader

## 準備

とりあえずは Linux を想定します. SwiftShader 自体はポータブルに書かれているので, 少しの変更で Windows, macOS でも動くものと思われます.

SwiftShader をコンパイルしておきます. Linux だとドキュメントにあるように引数なし cmake で問題なくコンパイルできるはずです. 


ただ, デフォルトだと exception off でビルドされるため, アプリ側が例外を使っているとうまくリンクできない可能性もあるかもしれません.

swiftshader では, Vulkan ライブラリは動的ロードするのを想定していいます.
`libvk_swiftshader.so` が vulkan 実装になります. 実行時にロードされるのを想定しているライブラリのためか, 普通の .so とはちょっとちがいます(C++ コードリンク時の利用は想定しておらず, `nm` 使ってもシンボルが出てきません)

したがってリンク時用のシンボル定義(Windows で言う import library に相当?)を用意しなくてはなりません.

そのための Vulkan-Loader がありますが,

https://github.com/KhronosGroup/Vulkan-Loader

ビルドに X11 とか Wayland も要求(cmake で off にはできるが)されて面倒なので, ここではとりあえず SwiftShader の unit test で使われているコードを使いまわします.
(後半のほうに swiftshader + VulkanLoader の組み合わせ版について記載があります)

https://github.com/google/swiftshader/tree/master/tests/VulkanUnitTests

の Driver, Device あたりにあります. 

自前のアプリでは Device.cpp, Driver.cpp を含めるようにし, Vulkan API を呼ぶところでは `Driver.hpp` をインクルードします. ただ, これは class で Vulkan API を wrap しているので, 直接(グローバル関数で) Vulkan API を呼ぶのはできません(リンク時に symbol が見つからないエラーになる).

### Windows(Visual Studio)での SwiftShader ビルド

Windows だと, VS2019 を使うとよいです(コマンドラインで cmake で .sln 生成だとなぜかうまくコンパイルできなかった). cmake の設定(CMakeSettings.json)が含まれています.

Visual Studio 2019 で Cmake + C++ プロジェクトを設定する CMakeSettings.json のメモ
https://qiita.com/syoyo/items/59177896c2c0cb3ffb59

VS2017 向けには一応 .sln が用意されています.

## 最小限のサンプル

最小限のコードはこんな感じになります.

```
#include <vulkan/vulkan.h> // SwiftShader の vulkan.h がインクルードされるのを想定

#include <iostream>
#include <cassert>

#include "Driver.hpp"

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

  Driver driver;
  if (!driver.loadSwiftShader()) {
    std::cerr << "failed to load SwiftShader\n";
    return -1;
  }

  uint32_t apiVersion = 0;
  VkResult result = driver.vkEnumerateInstanceVersion(&apiVersion);

  std::cout << "apiVersion " << apiVersion << "\n";

  const VkInstanceCreateInfo createInfo = {
      VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,  // sType
      nullptr,                                 // pNext
      0,                                       // flags
      nullptr,                                 // pApplicationInfo
      0,                                       // enabledLayerCount
      nullptr,                                 // ppEnabledLayerNames
      0,                                       // enabledExtensionCount
      nullptr,                                 // ppEnabledExtensionNames
  };
  VkInstance instance = VK_NULL_HANDLE;
  result = driver.vkCreateInstance(&createInfo, nullptr, &instance);
  assert(result == VK_SUCCESS);

  assert(driver.resolve(instance));

  uint32_t pPhysicalDeviceCount = 0;
  result = driver.vkEnumeratePhysicalDevices(instance, &pPhysicalDeviceCount, nullptr);
  assert(result == VK_SUCCESS);
  assert(pPhysicalDeviceCount == 1U);

  std::cout << "OK!\n";

  return 0;
}
```

`driver.loadSwiftShader()` で, Vulkan 実装の実体を `dlopen`, `dlsym` しています.

```
CXXFLAGS=-std=c++11 -fPIC -I/mnt/data/work/swiftshader/include -I/mnt/data/work/swiftshader/tests/VulkanUnitTests/

clang++-6.0 $(CXXFLAGS) main.cc /mnt/data/work/swiftshader/tests/VulkanUnitTests/Device.cpp  /mnt/data/work/swiftshader/tests/VulkanUnitTests/Driver.cpp -pthread -ldl
```

という感じでコンパイルすると, 最低限の Vulan を試すプログラムができます.

あとは `libvk_swiftshader.so` をコピーしてきて, `./a.out` すると動きます!

## Vulkan ライブラリロードの切り替え

GPU ベンダの vulkan ドライバ(システムの ICD ドライバ)を呼び出す関数が Driver.cpp にありました.

https://github.com/google/swiftshader/blob/master/tests/VulkanUnitTests/Driver.cpp#L98

これにより, まずは GPU(HW) Vulkan ドライバのロードを試みて, ダメだったら SwiftShader に fallback というのがアプリ側で単一コードでできますね. 

C++ アプリの場合, `vk****` 関数をグローバルで叩くより, この Driver class を使いまわして, Vulkan 関数を wrap しておいたほうが, のちのちコードの管理もやりやすそうです.
(Vulkan には c++ wrapper もあるのですが, https://github.com/KhronosGroup/Vulkan-Hpp , システムの vulkan header とのバージョンが違うとかでコンパイルうまくいかないケースがありあまりおすすめしません. ヘッダーは自動生成なのですが, この生成コードもうまくビルドできなかったり動かなかったりする)

## 既存 vulkan アプリを swiftshader で動かす確認(本格的な Vulkan アプリ開発で推奨の方法)

既存 vulkan アプリを swiftshader で動かしたり, これから Vulkan アプリ開発するのでしたら, Vulkan-Loader(+ Vulkan validation layers)を使います.

Vulkan-Loader https://github.com/KhronosGroup/Vulkan-Loader を使った vulkan アプリだと VK_ICD_FILENAMES で icd JSON ファイルを指定することで, swiftshader で実行することができます.

libvk_swiftshader.so と, vk_swiftshader_icd.json(swiftshader repo にあります)があるとします.

Ubuntu 18.04 で試します.
apt install vulkan-utils で `vulkaninfo` をインストールします.

```
$ export VK_ICD_FILENAMES=vk_swiftshader_icd.json 

$ vulkaninfo

...
GPU id     : 0(SwiftShader Device)

```

と出たら成功です!

また, vulkan_minimal_compute の fork の `swiftshader` branch で, Vulkan-Loader との組み合わせで動作するのを確認しました.

https://github.com/syoyo/vulkan_minimal_compute/tree/swiftshader

swiftshader, Vulkan-Loader など submodule いっぱいですが, self-contained なので別途 Vulkan SDK を用意するなどの必要はございません.

### Vulkan validation layers

Vulkan-Loader では, validation layers(API の呼び出しが正しいかのデバッグ用など)の動的ロードに対応しています.

新規で Vulkan アプリを書くには validation layers を有効にしないと開発もままなりませんし, 既存 Vulkan アプリで validation layers を要求するものもあるでしょう.

vulkan validation layer も使えるようにしておきましょう(swiftshader vulkan 自体には validation layer の実装は含まれていません)

https://github.com/KhronosGroup/Vulkan-ValidationLayers

Vullan-ValidationLayers も, 基本は環境変数 `VK_LAYER_PATH` で validation layers 実装(json 設定ファイルと, .so ライブラリ)を指定します.

有効にする layer は環境変数 `VK_INSTANCE_LAYERS` で指定します. その他詳細は Vulkan-ValidationLayers git repo にあるドキュメントを参照ください.

Vulkan-ValidationLayers のコンパイルは, glslang と Vulkan-Headers に依存してめんどいです.
vulkan_minimal_compute の `swiftshader` branch では ValidationLayers, glslang も submodule で取得するようにしています.

https://github.com/syoyo/vulkan_minimal_compute/tree/swiftshader

の `./scripts/build-validation-layers-linux.sh` にスクリプトを用意しましたので利用ください.

ValidationLayer コンパイルするには glslang を SPIRV-Tools 有効でビルドする必要があるが, SPIRV-Tools の取得は glslang 側の python script で取得する必要がありますが, そのあたりの手順も含んでいます.

Vulkan-ValidationLayers をビルドすると, `dist/share/vulkan/explicit_layers.d` に設定ファイルが, `/dist/lib` に .so ファイルができますので, 以下のように環境変数を設定します.

```
$ export VK_LAYER_PATH=`pwd`/dist/share/vulkan/explicit_layer.d/
$ export VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`/dist/lib
```

あとは今まで通りに vulkan アプリを実行します.

## TODO

* [x] SwiftShader での ICD の仕組みを調べる.
* [x] glew, glad のように実行時にシンボル解決するユーティリティライブラリを作り, 既存の Vulkan コードと統一をはかる(`driver.vkCreateInstance` -> `vkCreateInstance` としたい). => とりあえず Driver.cpp 使い回ししましょう
* [x] Vulkan Loader と SwiftShader vulkan の組みわせを確認する
* [x] SPIR-V シェーダの実行を確認する
* [x] Vulkan-Loader との組み合わせの記事を書く
* [x] VK_LAYER_LUNARG_standard_validation と VK_LAYER_KHRONOS_validation の違いがなにか調べる
  *  VK_LAYER_LUNARG_standard_validation は deprecated で, VK_LAYER_KHRONOS_validation がメインとなる
* [ ] 優秀な OpenGL/WebGL 若人さまが, SwiftShader Vulkan により, **人類史上最速で優秀な Vulkan 若人さまへと転生なされるスキーム**を確立する旅に出たい.