Большинство явных графических API не выполняют проверку ошибок, поскольку это может привести к снижению производительности. В Vulkan есть уровни проверки, которые позволяют выявлять ошибки во время разработки, не снижая производительность конечной сборки приложения. Уровни проверки основаны на механизме общего назначения, который перехватывает точки входа API.
Один уровень проверки Khronos
Раньше в Vulkan было несколько уровней проверки, которые нужно было включать в определенном порядке. Начиная с версии Vulkan SDK 1.1.106.0, в приложении достаточно включить один уровень проверки – VK_LAYER_KHRONOS_validation, чтобы получить все функции предыдущих уровней.
Как использовать уровни проверки, упакованные в APK
Чтобы обеспечить оптимальную совместимость, упакуйте уровни проверки в APK. Уровни проверки доступны в виде готовых двоичных файлов или могут быть собраны из исходного кода.
Как использовать готовые исполняемые файлы
Скачайте последние двоичные файлы уровня проверки Vulkan для Android со страницы выпуска на GitHub.
Проще всего добавить слои в 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
Как создать уровень проверки из исходного кода
Чтобы выполнить отладку исходного кода уровня проверки, скачайте последнюю версию кода из репозитория GitHub Khronos Group и следуйте инструкциям по сборке.
Убедитесь, что уровень проверки упакован правильно
Независимо от того, используете ли вы предварительно созданные слои 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 позволяет приложению включать слои во время создания экземпляра. Точки входа, которые перехватывает уровень, должны иметь один из следующих объектов в качестве первого параметра:
VkInstanceVkPhysicalDeviceVkDeviceVkCommandBufferVkQueue
Вызовите vkEnumerateInstanceLayerProperties(), чтобы получить список доступных слоев и их свойств. Vulkan включает слои при выполнении vkCreateInstance().
В следующем фрагменте кода показано, как приложение может использовать 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)
Включите обратный вызов отладки
Расширение Debug Utils VK_EXT_debug_utils позволяет приложению создавать отладочный мессенджер, который передает сообщения уровня проверки в обратный вызов, предоставляемый приложением. На вашем устройстве может не быть этого расширения, но оно реализовано в последних уровнях проверки. Также существует устаревшее расширение VK_EXT_debug_report, которое предоставляет аналогичные возможности, если расширение VK_EXT_debug_utils недоступно.
Прежде чем использовать расширение 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[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 значительно отличаются от других платформ. Чтобы загрузить внешние уровни проверки, должно выполняться одно из следующих условий:
Целевое приложение доступно для отладки. В этом случае вы получите больше информации для отладки, но производительность приложения может снизиться.
Целевое приложение запущено в сборке userdebug операционной системы, которая предоставляет root-доступ.
Только для приложений, предназначенных для 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 Debug Bridge (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:
$ 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>