大多数显式图形 API 都不会执行错误检查,因为执行错误检查会降低性能。Vulkan 提供可让您在开发时使用的错误检查功能,但该功能会从您应用的发布 build 中排除,这样可以避免在关键时刻性能出现下降。您可以通过启用 Vulkan 验证层来执行此操作。验证层会出于各种调试和验证目的截获或挂接 Vulkan 入口点。
验证层会截获其包含定义的入口点。未在层中定义的入口点都会到达基础级别的驱动程序,并保持未验证状态。
Android NDK 和 Vulkan 示例包括 Vulkan 验证层(可在开发期间使用)。您可以将验证层挂接到图形堆栈中,从而允许其报告验证问题。借助此插桩测试,您可以捕捉和修复开发期间出现的误用问题。
单个 Khronos 验证层
Vulkan 层可以通过加载程序插入到堆栈中,以便高级层调用底下的层,层堆栈最终终止于设备驱动程序。过去,在 Android 上按特定顺序启用了多个验证层。但是,现在使用单个层 VK_LAYER_KHRONOS_validation
,它包含了之前所有的验证层行为。对于 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
启用调试回调
调试实用工具扩展程序 VK_EXT_debug_utils
允许应用创建调试 messenger,将验证层消息传递给应用提供的回调。请注意,还有一个已弃用的扩展程序 VK_EXT_debug_report
,它可以在 VK_EXT_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 的安全模型和政策与其他平台有很大不同。若要加载外部层,必须满足以下条件之一:
- 目标应用的清单文件包含以下元数据元素(仅适用于以 Android 11(API 级别 30)或更高版本为目标的应用):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
您应使用此选项对应用进行剖析。 - 目标应用是可调试的。此选项可为您提供更多调试信息,但可能会降低应用性能。
- 目标应用在授予 root 访问权限的操作系统的 userdebug build 上运行。
加载层
搭载 Android 9(API 级别 28)和更高版本的设备允许 Vulkan 从应用的本地存储空间加载层。Android 10(API 级别 29)支持从单独的 APK 加载层。
- 设备的本地存储空间中的层二进制文件
- 包含层的 APK,与目标应用分开安装
设备的本地存储空间中的层二进制文件
Vulkan 会在设备的临时数据存储目录中寻找二进制文件,因此,您必须首先使用 Android 调试桥 (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 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>