Vulkan Extension Layer を使う Android アプリを RenderDoc でデバッグする


## 今回の結果の git コミット
fork した RenderDoc のリポジトリにコミットを追加してある.

https://github.com/James2022-rgb/renderdoc/commit/81e63e382068b9d8fffe79c69e012e5455a29226

後述の手順でビルドを行うと, VEL の `*.so` がパッケージングされた `org.renderdoc.renderdoccmd.arm64.apk` が生成される.

この APK を下記の手順で RenderDoc が使用するようにすると, VEL を使った RenderDoc キャプチャのリプレイが可能になる.

## 更新履歴

|日付      |内容                                             |
|----------|------------------------------------------------|
|2024-02-20|追記: APK の外部に存在する layer としての使用について|

## 導入

### Vulkan Extension Layer (VEL)
Android に限らず Vulkan アプリで, ドライバがネイティブで対応していない拡張を用いるために,
polyfill layer である [Vulkan Extension Layer (VEL)](https://github.com/KhronosGroup/Vulkan-ExtensionLayer) を用いることができる.

これらはドライバのアップデートをあまり期待できない Android において特に重宝し,
中でも `VK_KHR_synchronization2` や `VK_KHR_timeline_semaphore` のものを使いたい場合が多い.

### VEL と RenderDoc キャプチャ
ただし Android でこれら VEL を用いるアプリを RenderDoc でデバッグしたいとき,
キャプチャを取るまでは問題ないが, 実際にそのキャプチャをリプレイ(i.e 開く)する際には,
デバイスが該当の拡張に対応していない旨のエラーが表示され, 続行することができない.

![Current replaying hardware unsupported or incompatible with captured hardware](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100016/6aedd7ef-7b84-e916-1b4c-774c3d181485.png)
☝️ Adreno 650 のドライバ `512.444.0` がネイティブで `VK_KHR_synchronization2` に対応していないため, キャプチャをリプレイすることができない.

クロスプラットフォームな Vulkan アプリを開発する際,
`VK_KHR_synchronization2` や `VK_KHR_timeline_semaphore` は是非使用したいところだが,
その際ドライバの新しくない Android 端末にも対応する場合にはこれら VEL を使うことが必須になる.
ただしそのときこれら端末では, RenderDoc によるGPUデバッグが行えないことになる.

今回はこの問題を解決したい.

## Android における RenderDoc キャプチャのリプレイ

Android での RenderDoc キャプチャを開く際, 実際に行われるのは,
シリアライズされたグラフィクスAPIのコマンド列を Android 端末上の RenderDoc のアプリに送信して,
端末でそれらグラフィクスAPIのコマンド列を再実行(リプレイ)させることである.

この Android 端末上でのリプレイに用いられるアプリが,
`org.renderdoc.renderdoccmd.arm64` や `org.renderdoc.renderdoccmd.arm32` であり,
初回に Remote Context に接続した際に端末にインストールされる, お馴染みの画面のアプリになる.

![Honeyview_screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100016/824f5252-8bb6-1c2f-1afc-766d2f47879a.png)
☝️ `org.renderdoc.renderdoccmd.arm64` アプリ

## `org.renderdoc.renderdoccmd.arm64` と VEL

リプレイ時に, キャプチャされたグラフィクスAPIのコマンド列を実行しているのは実機上の `org.renderdoc.renderdoccmd.arm64` なので,
VEL の `*.so` を利用できる必要があるのは当然, このアプリということになる.

つまり, 自分のアプリで VEL を使用するときにように, VEL の `*.so` を `renderdoccmd` の APK の `lib/arm64-v8a` にパッケージング[^android_validation_layer_packaging][^wikipedia_apk_packaging]してしまうのが手っ取り早い:
```
lib/arm64-v8a/libVkLayer_khronos_synchronization2.so
lib/arm64-v8a/libVkLayer_khronos_timeline_semaphore.so
```

まずは基本的な `renderdoccmd` の Android 向けのビルドの手順を確認してから,
VEL の `*.so` のこの APK へのパッケージングができるようにしていく.

### APK の外部に存在する layer としての使用について
`*.so` に対して `org.renderdoc.renderdoccmd.arm64` で `run-as` を使用する方法[^run_as_method]だが,
これはうまくいかない.

`run-as` した `*.so` を認識させるには `adb shell setprop debug.vulkan.layers` など設定を行う必要があるが,
これらの設定を, RenderDoc は Android 端末の Remote Context の確立時に, 消去してしまう:
```cpp:renderdoc/renderdoc/android/android.cpp
Android::adbExecCommand(deviceID, "shell setprop debug.vulkan.layers :", ".", true);
Android::adbExecCommand(deviceID, "shell settings delete global enable_gpu_debug_layers", ".",
                          true);
Android::adbExecCommand(deviceID, "shell settings delete global gpu_debug_app", ".", true);
Android::adbExecCommand(deviceID, "shell settings delete global gpu_debug_layer_app", ".", true);
Android::adbExecCommand(deviceID, "shell settings delete global gpu_debug_layers", ".", true);
Android::adbExecCommand(deviceID, "shell settings delete global gpu_debug_layers_gles", ".", true);
```
https://github.com/baldurk/renderdoc/blob/52cfb0e08394b9e95cbbae774644a1271750397a/renderdoc/android/android.cpp#L634-L643

[^run_as_method]: [Load a validation layer binary from your device’s local storage - Vulkan validation layers on Android  |  Android NDK  |  Android Developers](https://developer.android.com/ndk/guides/graphics/validation-layer#layers-local-storage)

[^android_validation_layer_packaging]: [APK にパッケージ化された検証レイヤを使用する - Android の Vulkan 検証レイヤ  |  Android NDK  |  Android Developers](https://developer.android.com/ndk/guides/graphics/validation-layer?hl=ja#ilp)
[^wikipedia_apk_packaging]: [Package contents - apk (file format) - Wikipedia](https://en.wikipedia.org/wiki/Apk_(file_format)#Package_contents)

## Android 向けの `renderdoccmd` のビルドと使用

Windows で行いたかったが, 公式ドキュメントの推奨に従って, 今回は WSL 上の Ubuntu で行う.

### Android の開発環境の用意
Android SDK, Android NDK, Java Development Kit をインストールする.
[公式ドキュメントの該当部分](https://github.com/baldurk/renderdoc/blob/v1.x/docs/CONTRIBUTING/Dependencies.md#android-dependencies-on-linux)に従う.

### Android 向けのビルド

[公式ドキュメントの該当部分](https://github.com/baldurk/renderdoc/blob/v1.x/docs/CONTRIBUTING/Compiling.md#android)に従っていく.

`Ninja` をインストール:
```bash
sudo apt update
sudo apt-get -y install ninja-build
```

クローンした RenderDoc のディレクトリへ移動:
```bash
cd renderdoc
```

ビルド:
```bash
mkdir build-android
cd build-android
cmake -DBUILD_ANDROID=On -DANDROID_ABI=arm64-v8a -G "Ninja" ..
ninja -j 8
```

`ncurses5` が足りないようなことを言われたらインストール:
```bash
sudo apt-get -y install libncurses5
```

`org.renderdoc.renderdoccmd.arm64.apk` が, `renderdoc/build-android/bin` に生成されているはず.

### 使用

既に古い `org.renderdoc.renderdoccmd.arm64` が Android 端末に入っている場合, アンインストールしておく:
```bash
adb uninstall org.renderdoc.renderdoccmd.arm64
```

RenderDoc のインストールディレクトリの `plugins/android` に,
標準の `org.renderdoc.renderdoccmd.arm64` が存在するので, 新しいものをこれに上書きする.
これで, Remote Context 接続の際にインストールされるリプレイ用アプリが, 今回ビルドしたものになる.

## `renderdoccmd` に VEL の `*.so` がパッケージングされるようにする

`renderdoc/renderdoccmd/android` に, ABI ごとのディレクトリに `*.so` を入れたフォルダ `vel` を用意する:
```bash:renderdoc/renderdoccmd
renderdoccmd/
├── 3rdparty/
├── android/
│   ├── AndroidManifest.xml
│   ├── Loader.java
│   ├── icon.png
│   ├── vel/ 👈🆕
│   │   ├── arm64-v8a/
│   │   │   ├── libVkLayer_khronos_synchronization2.so
│   │   │   └── libVkLayer_khronos_timeline_semaphore.so
│   │   └── armeabi-v7a/
│   │       ├── libVkLayer_khronos_synchronization2.so
│   │       └── libVkLayer_khronos_timeline_semaphore.so
│   └── wrap.sh
├── apple/
├── ...
```

`renderdoc/renderdoccmd/CMakeLists.txt` に, これら `*.so` をパッケージングするための記述を追加する:
```diff_c_make:renderdoc/renderdoccmd/CMakeLists.txt
# Copy in android package files, replacing the package name with the architecture-specific package name
configure_file(android/Loader.java ${CMAKE_CURRENT_BINARY_DIR}/src/org/renderdoc/renderdoccmd/Loader.java)
configure_file(android/AndroidManifest.xml ${CMAKE_CURRENT_BINARY_DIR}/AndroidManifest.xml)
configure_file(android/icon.png ${CMAKE_CURRENT_BINARY_DIR}/res/drawable/icon.png COPYONLY)
+ configure_file(android/vel/${ANDROID_ABI}/libVkLayer_khronos_synchronization2.so ${CMAKE_CURRENT_BINARY_DIR}/vel/${ANDROID_ABI}/libVkLayer_khronos_synchronization2.so COPYONLY)
+ configure_file(android/vel/${ANDROID_ABI}/libVkLayer_khronos_timeline_semaphore.so ${CMAKE_CURRENT_BINARY_DIR}/vel/${ANDROID_ABI}/libVkLayer_khronos_timeline_semaphore.so COPYONLY)

add_custom_command(OUTPUT ${APK_FILE}
                   DEPENDS renderdoc
                   DEPENDS renderdoccmd
                   DEPENDS ${KEYSTORE}
                   WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
                   COMMAND ${CMAKE_COMMAND} -E make_directory libs/lib/${ANDROID_ABI}
                   COMMAND ${CMAKE_COMMAND} -E make_directory obj
                   COMMAND ${CMAKE_COMMAND} -E make_directory bin
                   COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:renderdoc> libs/lib/${ANDROID_ABI}/$<TARGET_FILE_NAME:renderdoc>
                   COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:renderdoccmd> libs/lib/${ANDROID_ABI}/$<TARGET_FILE_NAME:renderdoccmd>
+                  COMMAND ${CMAKE_COMMAND} -E copy vel/${ANDROID_ABI}/libVkLayer_khronos_synchronization2.so libs/lib/${ANDROID_ABI}/libVkLayer_khronos_synchronization2.so
+                  COMMAND ${CMAKE_COMMAND} -E copy vel/${ANDROID_ABI}/libVkLayer_khronos_timeline_semaphore.so libs/lib/${ANDROID_ABI}/libVkLayer_khronos_timeline_semaphore.so
                   )
```

この変更の上で上記の手順でビルドを行うと, `*.so` がパッケージングされた APK が生成される.

```bash:org.renderdoc.renderdoccmd.arm64.apk
org.renderdoc.renderdoccmd.arm64.apk/
├── AndroidManifest.xml
├── META-INF/
│   ├── MANIFEST.MF
│   ├── RDOCANDR.RSA
│   └── RDOCANDR.SF
├── classes.dex
├── lib/
│   └── arm64-v8a/
│       ├── libVkLayer_GLES_RenderDoc.so
│       ├── libVkLayer_khronos_synchronization2.so 👈🆕
│       ├── libVkLayer_khronos_timeline_semaphore.so 👈🆕
│       ├── librenderdoccmd.so
│       └── wrap.sh
├── res/
│   └── drawable/
│       └── icon.png
└── resources.arsc
```

この APK を上記の手順で RenderDoc が使用するようにすると, VEL を使った RenderDoc キャプチャのリプレイが可能になる.