Слои проверки Vulkan на Android

Большинство явных графических 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) следующим образом:

  1. Используйте команду adb push , чтобы загрузить двоичный файл слоя в хранилище данных вашего приложения на устройстве:

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. Используйте 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
    
  3. Включите слой .

Загрузите двоичный файл уровня проверки из другого APK

Вы можете использовать adb для установки APK , содержащего этот слой, а затем включить его .

adb install --abi abi path_to_apk

Включить слои вне приложения

Вы можете включить слои Vulkan как для каждого приложения, так и глобально. Настройки для каждого приложения сохраняются после перезагрузки, а глобальные свойства очищаются при перезагрузке.

Включение слоев для каждого приложения

Следующие шаги описывают, как включить слои для каждого приложения:

  1. Используйте настройки оболочки adb, чтобы включить слои:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. Укажите целевое приложение, в котором необходимо включить слои:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. Укажите список слоев для включения (сверху вниз), разделяя каждый слой двоеточием:

    $ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
    

    Поскольку у нас есть один уровень проверки Khronos, команда, скорее всего, будет выглядеть так:

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. Укажите один или несколько пакетов для поиска слоев внутри:

    $ 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>