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