Большинство явных графических API не выполняют проверку ошибок, поскольку это может привести к снижению производительности. Vulkan имеет слои проверки , которые обеспечивают проверку ошибок во время разработки, избегая снижения производительности в релизной сборке вашего приложения. Слои проверки основаны на универсальном механизме многоуровневой архитектуры, который перехватывает точки входа API.
Единый слой проверки Khronos
Ранее Vulkan предоставлял несколько слоев проверки, которые необходимо было включать в определенном порядке. Начиная с версии 1.1.106.0 Vulkan SDK, вашему приложению достаточно включить только один слой проверки , 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
Создайте слой проверки из исходного кода.
Для отладки исходного кода уровня проверки загрузите последнюю версию исходного кода из репозитория 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>