Большинство явных графических API не выполняют проверку ошибок, поскольку это может привести к снижению производительности. В Vulkan предусмотрены уровни валидации , которые обеспечивают проверку ошибок во время разработки, предотвращая снижение производительности в финальной сборке приложения. Уровни валидации основаны на универсальном механизме наложения слоёв, который перехватывает точки входа API.
Единый уровень проверки Khronos
Ранее Vulkan предоставлял несколько уровней валидации, которые требовалось включать в определённом порядке. Начиная с версии Vulkan SDK 1.1.106.0, вашему приложению достаточно включить только один уровень валидации — VK_LAYER_KHRONOS_validation
, чтобы получить доступ ко всем функциям предыдущих уровней валидации.
Используйте слои проверки, упакованные в ваш APK
Упаковка слоёв валидации в APK-файл обеспечивает оптимальную совместимость. Слои валидации доступны в виде готовых двоичных файлов или могут быть собраны из исходного кода.
Использовать готовые двоичные файлы
Загрузите последние двоичные файлы уровня проверки Android Vulkan со страницы релиза 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
Создайте слой проверки из исходного кода
Для отладки исходного кода уровня проверки извлеките последнюю версию исходного кода из репозитория 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
Включить уровень проверки во время создания экземпляра
API Vulkan позволяет приложению включать слои при создании экземпляра. Точки входа, перехватываемые слоем, должны иметь один из следующих объектов в качестве первого параметра:
-
VkInstance
-
VkPhysicalDevice
-
VkDevice
-
VkCommandBuffer
-
VkQueue
Вызовите vkEnumerateInstanceLayerProperties()
чтобы получить список доступных слоёв и их свойств. Vulkan включает слои при выполнении vkCreateInstance()
.
В следующем фрагменте кода показано, как приложение может использовать API Vulkan для программного запроса и включения слоев:
// 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 существенно отличаются от других платформ. Для загрузки внешних уровней проверки должно быть выполнено одно из следующих условий:
Целевое приложение можно отлаживать . Этот параметр предоставляет больше отладочной информации, но может негативно повлиять на производительность вашего приложения.
Целевое приложение запускается на отладочной сборке операционной системы, которая предоставляет права 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 для включения слоев:
$ 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>