パフォーマンスが低下する可能性があるため、明示的なグラフィック API のほとんどは、エラーチェックを行いません。Vulkan は、開発時にエラーチェックを行えるようにしていますが、アプリのリリースビルドからはエラーチェック機能を除外します。それによって、最も重要なときにパフォーマンスが低下するのを回避します。エラーチェックを行うには、Vulkan 検証レイヤを有効にします。検証レイヤは、さまざまなデバッグや検証を行うために、Vulkan エントリ ポイントをインターセプト(フック)します。
検証レイヤは、そこに含まれる定義に対応するエントリ ポイントをインターセプトします。レイヤに定義されていないエントリ ポイントは、検証されないままベースレベル、つまりドライバに到達します。
Android NDK と Vulkan のサンプルには、開発時に使用する Vulkan 検証レイヤが含まれています。検証レイヤをグラフィック スタックにフックすることで、検証での問題が報告されるようになります。このインストゥルメンテーションにより、開発中に誤りを検出して修正できます。
単一の Khronos 検証レイヤ
Vulkan レイヤはローダによってスタックに挿入され、レイヤスタックで上位のレイヤが下位のレイヤを呼び出し、最終的にデバイス ドライバで終了します。以前は、Android で特定の順序で複数の検証レイヤが有効になっていましたが、現在は、レイヤ VK_LAYER_KHRONOS_validation
1 つで、前述の検証レイヤの動作をすべて含むようになりました。Vulkan 検証では、すべてのアプリで単一の検証レイヤ VK_LAYER_KHRONOS_validation
を有効にする必要があります。
検証レイヤをパッケージ化する
NDK には検証レイヤのビルド済みバイナリが組み込まれており、これを APK にパッケージ化することにより、テストデバイスにプッシュできます。このバイナリはディレクトリndk-dir/sources/third_party/vulkan/src/build-android/jniLibs/abi/
にあります。アプリがリクエストすると、Vulkan ローダはアプリの APK からレイヤを見つけて読み込みます。
Gradle を使って検証レイヤを APK にパッケージ化する
Android Gradle プラグインと、Android Studio の CMake、ndk-build のサポートを利用して、検証レイヤをプロジェクトに追加できます。 Android Studio の CMake と ndk-build のサポートを使用してライブラリを追加するには、アプリ モジュールのbuild.gradle
ファイルに以下を追加します。
sourceSets { main { jniLibs { // Gradle includes libraries in the following path as dependencies // of your CMake or ndk-build project so that they are packaged in // your app’s APK. srcDir "ndk-path/sources/third_party/vulkan/src/build-android/jniLibs" } } }Android Studio の CMake と ndk-build のサポートについて詳しくは、プロジェクトへの C / C++ コードの追加をご覧ください。
検証レイヤを JNI ライブラリにパッケージ化する
次のコマンドライン オプションを使用すると、プロジェクトの JNI ライブラリ ディレクトリに検証レイヤのバイナリを手動で追加できます。$ cd project-root $ mkdir -p app/src/main $ cp -fr ndk-path/sources/third_party/vulkan/src/build-android/jniLibs app/src/main/
ソースからレイヤバイナリをビルドする
アプリで最新の検証レイヤが必要な場合は、Khronos Group の GitHub リポジトリから最新のソースを取得し、そこに記載のビルドの手順を行います。
レイヤのビルドを確認する
NDK の組み込みレイヤを使ってビルドする場合も、最新のソースコードからビルドする場合も、ビルドプロセスは以下のような最終的なファイル構造を生成します。
src/main/jniLibs/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so
次の例は、APK に想定どおりに検証レイヤが含まれているかどうかを検証する方法を示します。
$ jar -xvf project.apk ... inflated: lib/arm64-v8a/libVkLayer_khronos_validation.so ...
レイヤを有効にする
Vulkan API を使用すると、アプリでレイヤを有効にできます。レイヤはインスタンスの作成時に有効になります。レイヤがインターセプトするエントリ ポイントには、最初のパラメータとして以下のいずれかのオブジェクトが必要です。
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
vkEnumerateInstanceLayerProperties()
を呼び出すと、使用可能なレイヤとそのプロパティを一覧表示できます。システムは、vkCreateInstance()
の実行時にレイヤを有効にします。
次のコード スニペットは、アプリから Vulkan API を使用して、プログラムでレイヤに対して有効化やクエリを行う方法を示します。
// Get layer count using null pointer as last parameter uint32_t instance_layer_present_count = 0; vkEnumerateInstanceLayerProperties(&instance_layer_present_count, nullptr); // Enumerate layers with valid pointer in last parameter VkLayerProperties* layer_props = (VkLayerProperties*)malloc(instance_layer_present_count * sizeof(VkLayerProperties)); vkEnumerateInstanceLayerProperties(&instance_layer_present_count, layer_props)); // Make sure the desired validation layer is available const char *instance_layers[] = { "VK_LAYER_KHRONOS_validation" }; uint32_t instance_layer_request_count = sizeof(instance_layers) / sizeof(instance_layers[0]); for (uint32_t i = 0; i < instance_layer_request_count; i++) { bool found = false; for (uint32_t j = 0; j < instance_layer_present_count; j++) { if (strcmp(instance_layers[i], layer_props[j].layerName) == 0) { found = true; } } if (!found) { error(); } } // Pass desired layer into vkCreateInstance VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.enabledLayerCount = instance_layer_request_count; instance_info.ppEnabledLayerNames = instance_layers; ...
デフォルトの logcat 出力
検証レイヤは、VALIDATION
タグでラベル付けされた logcat に警告メッセージとエラー メッセージを出力します。検証レイヤのメッセージは次のようになります。
VALIDATION: UNASSIGNED-CoreValidation-DrawState-QueueForwardProgress(ERROR / SPEC): msgNum: 0 - VkQueue 0x7714c92dc0[] is waiting on VkSemaphore 0x192e[] that has no way to be signaled. VALIDATION: Objects: 1 VALIDATION: [0] 0x192e, type: 5, name: NULL
デバッグ コールバックを有効にする
Debug Utils 拡張機能 VK_EXT_debug_utils
により、アプリが提供するコールバックに検証レイヤのメッセージを渡すデバッグ メッセンジャーをアプリで作成できます。また、サポートを終了する拡張機能 VK_EXT_debug_report
もあります。これは、VK_EXT_debug_utils
が使用できない場合に同様の機能を提供します。
使用する前に、Debug Utils 拡張機能がプラットフォームでサポートされているかどうかを確認する必要があります。次の例は、デバッグ拡張機能がサポートされているかどうかを確認し、サポートされている場合はコールバックを登録する方法を示します。
// Get the instance extension count uint32_t inst_ext_count = 0; vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, nullptr); // Enumerate the instance extensions VkExtensionProperties* inst_exts = (VkExtensionProperties *)malloc(inst_ext_count * sizeof(VkExtensionProperties)); vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, inst_exts); const char * enabled_inst_exts[16] = {}; uint32_t enabled_inst_ext_count = 0; // Make sure the debug utils extension is available for (uint32_t i = 0; i < inst_ext_count; i++) { if (strcmp(inst_exts[i].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { enabled_inst_exts[enabled_inst_ext_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; } } if (enabled_inst_ext_count == 0) return; // Pass the instance extensions into vkCreateInstance VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.enabledExtensionCount = enabled_inst_ext_count; instance_info.ppEnabledExtensionNames = enabled_inst_exts; PFN_vkCreateDebugUtilsMessengerEXT pfnCreateDebugUtilsMessengerEXT; PFN_vkDestroyDebugUtilsMessengerEXT pfnDestroyDebugUtilsMessengerEXT; pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetDeviceProcAddr(device, "vkCreateDebugUtilsMessengerEXT"); pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetDeviceProcAddr(device, "vkDestroyDebugUtilsMessengerEXT"); assert(pfnCreateDebugUtilsMessengerEXT); assert(pfnDestroyDebugUtilsMessengerEXT); // Create the debug messenger callback with desired settings VkDebugUtilsMessengerEXT debugUtilsMessenger; if (pfnCreateDebugUtilsMessengerEXT) { VkDebugUtilsMessengerCreateInfoEXT messengerInfo; constexpr VkDebugUtilsMessageSeverityFlagsEXT kSeveritiesToLog = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; constexpr VkDebugUtilsMessageTypeFlagsEXT kMessagesToLog = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; messengerInfo.pNext = nullptr; messengerInfo.flags = 0; messengerInfo.messageSeverity = kSeveritiesToLog; messengerInfo.messageType = kMessagesToLog; messengerInfo.pfnUserCallback = &DebugUtilsMessenger; // Callback example below messengerInfo.pUserData = nullptr; // Custom user data passed to callback pfnCreateDebugUtilsMessengerEXT(instance, &messengerInfo, nullptr, &debugUtilsMessenger); } // Later, when shutting down Vulkan, call the following if (pfnDestroyDebugUtilsMessengerEXT) { pfnDestroyDebugUtilsMessengerEXT(instance, debugUtilsMessenger, nullptr); }
アプリがデバッグ コールバックを登録して有効にすると、登録したコールバックにシステムがデバッグ メッセージを渡します。このようなコールバックの例を下記に示します。
#include <android/log.h> VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsMessenger( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT *callbackData, void *userData) { const char validation[] = "Validation"; const char performance[] = "Performance"; const char error[] = "ERROR"; const char warning[] = "WARNING"; const char unknownType[] = "UNKNOWN_TYPE"; const char unknownSeverity[] = "UNKNOWN_SEVERITY"; const char* typeString = unknownType; const char* severityString = unknownSeverity; const char* messageIdName = callbackData->pMessageIdName; int32_t messageIdNumber = callbackData->messageIdNumber; const char* message = callbackData->pMessage; android_LogPriority priority = ANDROID_LOG_UNKNOWN; if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { severityString = error; priority = ANDROID_LOG_ERROR; } else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { severityString = warning; priority = ANDROID_LOG_WARN; } if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { typeString = validation; } else if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { typeString = performance; } __android_log_print(priority, "AppName", "%s %s: [%s] Code %i : %s", typeString, severityString, messageIdName, messageIdNumber, message); // Returning false tells the layer not to stop when the event occurs, so // they see the same behavior with and without validation layers enabled. return VK_FALSE; }
ADB を使用してレイヤをテストデバイスにプッシュする
テストデバイスにレイヤをプッシュするには、このセクションの手順を実施します。
デバッグを有効にする
Android のセキュリティ モデルとポリシーは他のプラットフォームと大きく異なります。外部レイヤを読み込むには、次のいずれかの条件を満たす必要があります。
- ターゲット アプリのマニフェスト ファイルに次の meta-data 要素が含まれている。ただし、これは Android 11(API レベル 30)以降をターゲットとするアプリにのみ適用されます。
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
このオプションを使用してアプリをプロファイリングする必要があります。 - ターゲット アプリがデバッグ可能である。このオプションを使用すると、より詳細なデバッグ情報を得られますが、アプリのパフォーマンスに悪影響を及ぼす可能性があります。
- ルートアクセスを許可するオペレーティング システムの userdebug ビルドでターゲット アプリが実行されている。
レイヤを読み込む
Android 9(API レベル 28)以上を搭載するデバイスでは、Vulkan はアプリのローカル ストレージからレイヤを読み込むことができます。Android 10(API レベル 29)では、個別の APK からレイヤを読み込むことができます。
- デバイスのローカル ストレージにあるレイヤバイナリ
- ターゲット アプリとは別にインストールされた、レイヤを含む APK
デバイスのローカル ストレージにあるレイヤバイナリ
Vulkan はデバイスの一時データ ストレージ ディレクトリでバイナリを検索するので、最初に Android Debug Bridge(ADB)を使用してそのディレクトリにバイナリをプッシュする必要があります(以下参照)。
-
adb push
コマンドを使用して、必要なレイヤバイナリをデバイス上のアプリのデータ ストレージに読み込みます。次の例では、libVkLayer_khronos_validation.so
をデバイスの/data/local/tmp
ディレクトリにプッシュします。$ adb push libVkLayer_khronos_validation.so /data/local/tmp
-
adb shell
コマンドとrun-as
コマンドを使用して、アプリのプロセスを介してレイヤを読み込みます。これにより、バイナリは root アクセス権を必要とせずに、アプリと同じデバイス アクセス権を持つことになります。$ adb shell run-as com.example.myapp cp /data/local/tmp/libVkLayer_khronos_validation.so . $ adb shell run-as com.example.myapp ls libVkLayer_khronos_validation.so
- レイヤを有効にします。
レイヤを含む APK
adb
を使って、APK をインストールしてから、レイヤを有効にします。
adb install --abi abi path_to_apk
アプリの外部のレイヤを有効にする
レイヤは、アプリレベルかグローバル レベルで有効にできます。アプリレベルの設定は再起動後も保持されますが、グローバル プロパティは再起動時にクリアされます。
アプリレベルでレイヤを有効にするには:
# Enable layers adb shell settings put global enable_gpu_debug_layers 1 # Specify target application adb shell settings put global gpu_debug_app <package_name> # Specify layer list (from top to bottom) adb shell settings put global gpu_debug_layers <layer1:layer2:layerN> # Specify packages to search for layers adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
設定が有効になっているかどうかを確認するには、次のコマンドを使用します。
$ adb shell settings list global | grep gpu enable_gpu_debug_layers=1 gpu_debug_app=com.example.myapp gpu_debug_layers=VK_LAYER_KHRONOS_validation
適用した設定はデバイスの再起動後も保持されるため、レイヤを読み込んだ後はこの設定を削除することをおすすめします。
$ adb shell settings delete global enable_gpu_debug_layers $ adb shell settings delete global gpu_debug_app $ adb shell settings delete global gpu_debug_layers $ adb shell settings delete global gpu_debug_layer_app
アプリレベルでレイヤを無効にするには:
# Delete the global setting that enables layers adb shell settings delete global enable_gpu_debug_layers # Delete the global setting that selects target application adb shell settings delete global gpu_debug_app # Delete the global setting that specifies layer list adb shell settings delete global gpu_debug_layers # Delete the global setting that specifies layer packages adb shell settings delete global gpu_debug_layer_app
グローバル レベルでレイヤを有効にするには:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>