Warstwy walidacji interfejsu Vulkan na Androidzie

Większość interfejsów API do obsługi grafiki dla pełnoletnich nie sprawdza błędów, ponieważ może to skutkuje karą za wydajność. Interfejs Vulkan ma warstwy weryfikacji, które zapewniają sprawdzania błędów w trakcie programowania, unikniesz spadku wydajności wersji aplikacji. Warstwy walidacji są ogólnym przeznaczeniem który przechwytuje punkty wejścia interfejsu API.

Warstwa walidacji pojedynczego chronosu

Wcześniej Vulkan udostępniał wiele warstw walidacji, które trzeba było włączyć. w określonej kolejności. Od wersji 1.1.106.0 pakietu SDK Vulkan Twoja aplikacja wystarczy włączyć jedną weryfikację, warstwa, VK_LAYER_KHRONOS_validation, aby uzyskać dostęp do wszystkich funkcji z poprzedniej wersji warstw weryfikacji.

Użyj warstw weryfikacji spakowanych w pliku APK

Optymalną zgodność zapewnia warstwy weryfikacji pakietu w pakiecie APK. Warstwy weryfikacji są dostępne jako gotowe pliki binarne lub można je skompilować na podstawie kodu źródłowego.

Użyj gotowych plików binarnych

Pobierz najnowsze pliki binarne warstwy weryfikacji Vulkana na Androidzie z GitHub stronie wersji.

Najprostszym sposobem dodania warstw do pliku APK jest wyodrębnienie gotowej warstwy. pliki binarne do katalogu src/main/jniLibs/ Twojego modułu, korzystając z interfejsu ABI (takich jak arm64-v8a lub x86-64) niezmienione, tak jak poniżej:

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

Tworzenie warstwy weryfikacji na podstawie kodu źródłowego

Aby przeprowadzić debugowanie do kodu źródłowego warstwy weryfikacji, pobierz najnowsze źródło z Grupa Khronos GitHub z repozytorium i obserwuj z instrukcją tworzenia kreacji.

Sprawdzanie, czy warstwa weryfikacji jest prawidłowo spakowana

Niezależnie od tego, czy budujesz z użyciem gotowych warstw Khronos, czy z warstw ze źródła, proces kompilacji tworzy w pliku APK ostateczną strukturę pliku, taką jak następujące:

lib/
  arm64-v8a/
    libVkLayer_khronos_validation.so
  armeabi-v7a/
    libVkLayer_khronos_validation.so
  x86/
    libVkLayer_khronos_validation.so
  x86-64/
    libVkLayer_khronos_validation.so

Poniżej znajdziesz instrukcje, jak sprawdzić, czy pakiet APK zawiera weryfikację warstwa zgodnie z oczekiwaniami:

$ 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

Włączanie warstwy weryfikacji podczas tworzenia instancji

Interfejs Vulkan API umożliwia aplikacji włączanie warstw podczas tworzenia instancji. Wpis przechwycone przez warstwę muszą mieć jeden z następujących obiektów jako pierwszy parametr:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

Zadzwoń pod numer vkEnumerateInstanceLayerProperties() by wyświetlić listę dostępnych warstw i ich właściwości. Vulkan włącza warstwy, gdy: vkCreateInstance() .

Ten fragment kodu pokazuje, jak aplikacja może używać interfejsu Vulkan API do programistyczne zapytania i włączanie warstw:

// 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,

Domyślne dane wyjściowe logcat

Warstwa weryfikacji generuje ostrzeżenia i komunikaty o błędach w narzędziu LogCat z etykietą VALIDATION. Komunikat warstwy weryfikacji wygląda tak (z podziały wierszy, aby ułatwić przewijanie):

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)

Włącz wywołanie zwrotne debugowania

Rozszerzenie VK_EXT_debug_utils Debug Utils pozwala aplikacji utworzyć komunikator debugowania, który przekazuje komunikaty warstwy weryfikacji do dostarczonej przez aplikację aplikacji oddzwanianie. Twoje urządzenie może nie obsługiwać tego rozszerzenia, ale zostało ono zaimplementowane w najnowsze warstwy walidacji. Istnieje również wycofane rozszerzenie o nazwie VK_EXT_debug_report, która zapewnia podobne możliwości, jeśli Opcja VK_EXT_debug_utils jest niedostępna.

Przed użyciem rozszerzenia Debug utils upewnij się, że urządzenie lub obsługuje ją załadowana warstwa weryfikacji. Ten przykład pokazuje, jak Sprawdź, czy rozszerzenie narzędzia do debugowania jest obsługiwane, i zarejestruj wywołanie zwrotne, jeśli rozszerzenie jest obsługiwane przez urządzenie lub warstwę walidacji.

// 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);
}

Gdy aplikacja się zarejestruje i włączy wywołanie zwrotne, system przekieruje debugowanie wiadomości do niej.

#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;
}

Korzystanie z zewnętrznych warstw weryfikacji

Nie musisz tworzyć pakietów warstw walidacji w pliku APK. urządzenia z Androidem 9 (poziom interfejsu API 28) i nowsze mogą używać warstw weryfikacji poza Twoim plikiem binarnym i dynamicznie je włączaj. Wykonaj czynności opisane w tej sekcji, aby przesłać kilka warstw walidacji na urządzeniu testowym:

Włącz w aplikacji używanie zewnętrznych warstw weryfikacji

Model zabezpieczeń na Androidzie i zasady znacznie się różnią od innych platform. Aby wczytać zewnętrzne warstwy walidacji, należy spełnić jeden z tych warunków: musi mieć wartość prawda:

  • Aplikację docelową można debugować. Ta opcja powoduje więcej błędów debugowania informacje, ale mogą negatywnie wpływać na działanie aplikacji.

  • Aplikacja docelowa jest uruchamiana w kompilacji userdebug systemu operacyjnego, przyznaje dostęp na poziomie roota.

  • Aplikacje kierowane tylko na Androida 11 (poziom API 30) lub nowszego: Twój docelowy Android plik manifestu zawiera następujące elementy: Element meta-data:

    <meta-data android:name="com.android.graphics.injectLayers.enable"
      android:value="true"/>
    

Wczytywanie zewnętrznej warstwy walidacji

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub nowszym Vulkan może wczytywać warstwy weryfikacji z pamięci lokalnej aplikacji. Uruchamiam w Androidzie 10 (poziom interfejsu API 29) Vulkan może również wczytywać warstwę walidacji z oddzielnym plikiem APK. Możesz wybrać dowolną metodę o ile Twoja wersja Androida obsługuje tę funkcję.

Wczytaj plik binarny warstwy weryfikacji z pamięci lokalnej urządzenia

Ponieważ Vulkan szuka pliku binarnego w tymczasowym miejscu na dane urządzenia najpierw wypchnij do niego plik binarny za pomocą narzędzia Debugowanie Androida Bridge (adb):

  1. Użyj polecenia adb push, aby wczytać umieść plik binarny w pamięci danych aplikacji na urządzeniu:

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. Skorzystaj z narzędzi adb shell i run-as polecenia ładowania warstwy w procesie aplikacji. Oznacza to, że plik binarny ma taki sam dostęp do urządzenia, jaki ma aplikacja, ale nie wymaga dostępu na poziomie roota.

    $ 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. Włącz warstwę.

Wczytaj plik binarny warstwy weryfikacji z innego pliku APK

Za pomocą adb możesz zainstalować plik APK, który obejmuje warstwę, po czym włącz ją.

adb install --abi abi path_to_apk

Włącz warstwy spoza aplikacji

Warstwy interfejsu Vulkan możesz włączyć dla poszczególnych aplikacji lub globalnie. Ustawienia dla poszczególnych aplikacji trwają po ponownym uruchomieniu, natomiast właściwości globalne są wyczyszczone i uruchomić go ponownie.

Włączanie warstw dla poszczególnych aplikacji

Aby włączyć warstwy dla poszczególnych aplikacji:

  1. Użyj ustawień powłoki adb, aby włączyć warstwy:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. Określ aplikację docelową, w której zostaną włączone warstwy:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. Określ listę warstw do włączenia (od góry do dołu), oddzielając poszczególne dwukropek:

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

    Ponieważ mamy jedną warstwę walidacji Khronos, polecenie prawdopodobnie wyglądają jak:

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. Określ co najmniej jeden pakiet, w którym chcesz wyszukać warstwy:

    $ adb shell settings put global
      gpu_debug_layer_app <package1:package2:packageN>
    
.

Aby sprawdzić, czy ustawienia są włączone, użyj tych poleceń:

$ 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

Zastosowane ustawienia są zachowywane po ponownym uruchomieniu urządzenia, więc warto wyczyść ustawienia po załadowaniu warstw:

$ 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

Włącz warstwy globalnie

Do czasu ponownego uruchomienia możesz włączyć globalnie jedną lub więcej warstw. Spowoduje to próbę wczytania warstw dla wszystkich aplikacji, w tym natywnych pliki wykonywalne.

$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>