大多数显式图形 API 不会执行错误检查,因为这样做会导致性能下降。Vulkan 具有验证层,可在开发过程中提供错误检查,从而避免在应用的发布版本中出现性能下降。验证层依赖于拦截 API 入口点的通用分层机制。
单个 Khronos 验证层
以前,Vulkan 提供了多个需要按特定顺序启用的验证层。从 1.1.106.0 Vulkan SDK 版本开始,您的应用只需启用一个单个验证层,VK_LAYER_KHRONOS_validation
,即可获得以前验证层的所有功能。
使用 APK 中打包的验证层
在 APK 中打包验证层可确保最佳兼容性。验证层可作为预构建二进制文件使用,也可以从源代码构建。
使用预构建二进制文件
从GitHub 发行页面下载最新的 Android Vulkan 验证层二进制文件。
将层添加到 APK 的最简单方法是将预构建层二进制文件解压缩到模块的src/main/jniLibs/
目录中,并保持 ABI 目录(如arm64-v8a
或x86-64
)不变,如下所示
src/main/jniLibs/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so x86/ libVkLayer_khronos_validation.so x86-64/ libVkLayer_khronos_validation.so
从源代码构建验证层
要调试到验证层源代码,请从 Khronos Group 的GitHub 存储库中提取最新的源代码,并按照那里的构建说明进行操作。
验证验证层是否已正确打包
无论您是使用 Khronos 预构建层还是从源代码构建的层进行构建,构建过程都会在您的 APK 中生成如下所示的最终文件结构
lib/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so x86/ libVkLayer_khronos_validation.so x86-64/ libVkLayer_khronos_validation.so
以下命令显示如何验证您的 APK 是否按预期包含验证层
$ jar -tf project.apk | grep libVkLayer lib/x86_64/libVkLayer_khronos_validation.so lib/armeabi-v7a/libVkLayer_khronos_validation.so lib/arm64-v8a/libVkLayer_khronos_validation.so lib/x86/libVkLayer_khronos_validation.so
在实例创建期间启用验证层
Vulkan API 允许应用在实例创建期间启用层。层拦截的入口点必须具有以下对象之一作为第一个参数
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
调用vkEnumerateInstanceLayerProperties()
列出可用层及其属性。当vkCreateInstance()
执行时,Vulkan 会启用层。
以下代码片段显示了应用如何使用 Vulkan API 以编程方式查询和启用层
// Enable just the Khronos validation layer. static const char *layers[] = {"VK_LAYER_KHRONOS_validation"}; // Get the layer count using a null pointer as the last parameter. uint32_t instance_layer_present_count = 0; vkEnumerateInstanceLayerProperties(&instance_layer_present_count, nullptr); // Enumerate layers with a valid pointer in the last parameter. VkLayerProperties layer_props[instance_layer_present_count]; vkEnumerateInstanceLayerProperties(&instance_layer_present_count, layer_props); // Make sure selected validation layers are available. VkLayerProperties *layer_props_end = layer_props + instance_layer_present_count; for (const char* layer:layers) { assert(layer_props_end != std::find_if(layer_props, layer_props_end, [layer](VkLayerProperties layerProperties) { return strcmp(layerProperties.layerName, layer) == 0; })); } // Create a Vulkan instance, requesting all enabled layers or extensions // available on the system VkInstanceCreateInfo instanceCreateInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .pApplicationInfo = &appInfo, .enabledLayerCount = sizeof(layers) / sizeof(layers[0]), .ppEnabledLayerNames = layers,
默认 logcat 输出
验证层会在 logcat 中输出警告和错误消息,并用VALIDATION
标签标记。验证层消息如下所示(此处添加换行符以便于滚动)
Validation -- Validation Error: [ VUID-VkDeviceQueueCreateInfo-pQueuePriorities-parameter ] Object 0: VK_NULL_HANDLE, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xd6d720c6 | vkCreateDevice: required parameter pCreateInfo->pQueueCreateInfos[0].pQueuePriorities specified as NULL. The Vulkan spec states: pQueuePriorities must be a valid pointer to an array of queueCount float values (https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html #VUID-VkDeviceQueueCreateInfo-pQueuePriorities-parameter)
启用调试回调
调试实用程序扩展VK_EXT_debug_utils
允许您的应用程序创建一个调试信使,将验证层消息传递给应用程序提供的回调。您的设备可能未实现此扩展,但它在最新的验证层中已实现。还有一个名为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[inst_ext_count]; vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, inst_exts); // Check for debug utils extension within the system driver or loader. // Check if the debug utils extension is available (in the driver). VkExtensionProperties *inst_exts_end = inst_exts + inst_ext_count; bool debugUtilsExtAvailable = inst_exts_end != std::find_if(inst_exts, inst_exts_end, [](VkExtensionProperties extensionProperties) { return strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }); if ( !debugUtilsExtAvailable ) { // Also check the layers for the debug utils extension. for (auto layer: layer_props) { uint32_t layer_ext_count; vkEnumerateInstanceExtensionProperties(layer.layerName, &layer_ext_count, nullptr); if (layer_ext_count == 0) continue; VkExtensionProperties layer_exts[layer_ext_count]; vkEnumerateInstanceExtensionProperties(layer.layerName, &layer_ext_count, layer_exts); VkExtensionProperties * layer_exts_end = layer_exts + layer_ext_count; debugUtilsExtAvailable = layer_exts != std::find_if( layer_exts, layer_exts_end,[](VkExtensionProperties extensionProperties) { return strcmp(extensionProperties.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }); if (debugUtilsExtAvailable) { // Add the including layer into the layer request list if necessary. break; } } } if (!debugUtilsExtAvailable) return; // since this snippet depends on debugUtils const char * enabled_inst_exts[] = { ..., VK_EXT_DEBUG_UTILS_EXTENSION_NAME }; uint32_t enabled_extension_count = sizeof(enabled_inst_exts)/sizeof(enabled_inst_exts[0]); // Pass the instance extensions into vkCreateInstance. VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.enabledExtensionCount = enabled_extension_count; instance_info.ppEnabledExtensionNames = enabled_inst_exts; // NOTE: Can still return VK_ERROR_EXTENSION_NOT_PRESENT if validation layer // isn't loaded. vkCreateInstance(&instance_info, nullptr, &instance); auto pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( tutorialInstance, "vkCreateDebugUtilsMessengerEXT"); auto pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( tutorialInstance, "vkDestroyDebugUtilsMessengerEXT"); // Create the debug messenger callback with your the settings you want. 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; // The DebugUtilsMessenger callback is explained in the following section. messengerInfo.pfnUserCallback = &DebugUtilsMessenger; 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; }
使用外部验证层
您无需将验证层打包到 APK 中;运行 Android 9(API 级别 28)及更高版本的设备可以使用二进制文件外部的验证层,并动态地打开和关闭它们。请按照本节中的步骤将验证层推送到您的测试设备
启用您的应用程序以使用外部验证层
Android 的安全模型和策略与其他平台有很大差异。要加载外部验证层,以下条件之一必须为真
目标应用是可调试的。此选项会产生更多调试信息,但可能会对应用程序的性能产生负面影响。
目标应用程序在授予 root 访问权限的操作系统的userdebug版本上运行。
面向 Android 11(API 级别 30)或更高版本的应用:您的目标 Android 清单文件包含以下
meta-data
元素<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
加载外部验证层
运行 Android 9(API 级别 28)及更高版本的设备允许 Vulkan从应用程序的本地存储加载验证层。从 Android 10(API 级别 29)开始,Vulkan 还可以从单独的 APK 加载验证层。您可以选择任何您喜欢的方法,只要您的 Android 版本支持即可。
从设备的本地存储加载验证层二进制文件
由于 Vulkan 在设备的临时数据存储目录中查找二进制文件,因此您必须首先使用Android 调试桥 (adb)将二进制文件推送到该目录,如下所示
使用
adb push
命令将层二进制文件加载到设备上的应用程序数据存储中$ 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
在应用程序外部启用层
您可以为每个应用程序或全局启用 Vulkan 层。每个应用程序的设置会保留在重新启动后,而全局属性在重新启动时会被清除。
基于每个应用程序启用层
以下步骤介绍了如何在每个应用程序的基础上启用层
使用 adb shell settings 启用层
$ adb shell settings put global enable_gpu_debug_layers 1
指定要启用层的目标应用程序
$ adb shell settings put global gpu_debug_app <package_name>
指定要启用的层列表(从上到下),用冒号分隔每个层
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
由于我们只有一个 Khronos 验证层,因此命令可能如下所示
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
指定一个或多个要搜索层内的包
$ 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
全局启用层
您可以全局启用一个或多个层,直到下次重新启动。这会尝试为所有应用程序(包括本机可执行文件)加载层。
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>