Większość interfejsów API do obsługi grafiki dla pełnoletnich nie sprawdza błędów, ponieważ może to skutkować obniżeniem wydajności. Interfejs Vulkan ma warstwy weryfikacji, które umożliwiają wykrywanie błędów w trakcie programowania, co pozwala uniknąć spadku wydajności w kompilacji do publikacji. Warstwy weryfikacji wykorzystują ogólny mechanizm tworzenia warstw, który przechwytuje punkty wejścia interfejsu API.
Warstwa walidacji pojedynczego chronosu
Wcześniej Vulkan udostępniał wiele warstw weryfikacji, które trzeba było włączać w określonej kolejności. Od wersji pakietu SDK Vulkan od wersji 1.1.106.0 Twoja aplikacja musi włączyć tylko jedną warstwę weryfikacji (VK_LAYER_KHRONOS_validation
), aby uzyskać wszystkie funkcje z poprzednich 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
Najnowsze pliki binarne warstwy weryfikacji Vulkana na Androidzie możesz pobrać ze strony wersji GitHub.
Najprostszym sposobem na dodanie warstw do pliku APK jest wyodrębnienie gotowych plików binarnych warstwy do katalogu src/main/jniLibs/
Twojego modułu bez zmian w katalogach ABI (takich jak arm64-v8a
czy x86-64
). Na przykład:
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 repozytorium GitHub grupy Khronos i postępuj zgodnie z wyświetlanymi tam instrukcjami kompilacji.
Sprawdzanie, czy warstwa weryfikacji jest prawidłowo spakowana
Niezależnie od tego, czy kompilujesz z użyciem gotowych warstw Khronos, czy warstw utworzonych na podstawie źródła, proces kompilacji tworzy w pakiecie APK ostateczną strukturę pliku w ten sposób:
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 warstwę weryfikacji 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. Punkty wejścia, które przechwytuje warstwa, muszą mieć jako pierwszy parametr jeden z tych obiektów:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
Wywołaj vkEnumerateInstanceLayerProperties()
, aby wyświetlić listę dostępnych warstw i ich właściwości. Interfejs Vulkan włącza warstwy podczas wykonywania skryptu vkCreateInstance()
.
Ten fragment kodu pokazuje, jak aplikacja może używać interfejsu Vulkan API do programowego wysyłania zapytań i włączania 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 oznaczonym tagiem VALIDATION
. Komunikat warstwy weryfikacji wygląda tak (po dodaniu podziałów wierszy, które ułatwiają 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 Utilitys pozwala aplikacji utworzyć komunikat debugowania, który przekazuje komunikaty warstwy weryfikacji do wywołania zwrotnego dostarczonego przez aplikację. Twoje urządzenie może nie implementować tego rozszerzenia, ale jest ono zaimplementowane w najnowszych warstwach weryfikacji. Istnieje też wycofane rozszerzenie o nazwie VK_EXT_debug_report
, które zapewnia podobne możliwości w razie braku rozszerzenia VK_EXT_debug_utils
.
Przed użyciem tego rozszerzenia upewnij się, że Twoje urządzenie lub załadowana warstwa weryfikacji je obsługuje. Z przykładu poniżej dowiesz się, jak sprawdzić, czy rozszerzenie narzędzi do debugowania jest obsługiwane i jak zarejestrować wywołanie zwrotne, jeśli jest ono 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 do niej komunikaty debugowania.
#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 umieszczać w pakiecie warstw weryfikacji. Urządzenia z Androidem 9 (poziom interfejsu API 28) lub nowszym mogą korzystać z warstw weryfikacji poza Twoim plikiem binarnym oraz dynamicznie je włączać i wyłączać. Wykonaj czynności opisane w tej sekcji, aby przekazać warstwy weryfikacji na urządzenie testowe:
Włącz w aplikacji używanie zewnętrznych warstw weryfikacji
Model zabezpieczeń i zasady Androida znacznie różnią się od tych na innych platformach. Aby wczytać zewnętrzne warstwy walidacji, musi być spełniony jeden z tych warunków:
Aplikację docelową można debugować. Dzięki temu uzyskasz więcej informacji na potrzeby debugowania, ale może to negatywnie wpłynąć na wydajność aplikacji.
Aplikacja docelowa jest uruchamiana w kompilacji userdebug systemu operacyjnego, który przyznaje dostęp na poziomie roota.
Aplikacje kierowane tylko na Androida 11 (poziom interfejsu API 30) lub nowszego: docelowy plik manifestu na Androida zawiera ten element
meta-data
:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Wczytywanie zewnętrznej warstwy walidacji
Urządzenia z Androidem 9 (poziom interfejsu API 28) lub nowszym umożliwiają usłudze Vulkan wczytywanie warstwy weryfikacji z pamięci lokalnej aplikacji. Począwszy od Androida 10 (poziom interfejsu API 29) Vulkan może też wczytywać warstwę weryfikacji z oddzielnego pliku APK. Możesz wybrać dowolną metodę, o ile obsługuje ją Twoja wersja Androida.
Wczytaj plik binarny warstwy weryfikacji z pamięci lokalnej urządzenia
Jako że Vulkan szuka pliku binarnego w katalogu tymczasowego przechowywania danych na urządzeniu, musisz najpierw przekazać go do tego katalogu przy użyciu narzędzia Android DebugBridge (adb) w ten sposób:
Aby wczytać plik binarny warstwy do miejsca na dane aplikacji na urządzeniu, użyj polecenia
adb push
:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Użyj poleceń
adb shell
irun-as
, aby wczytać warstwę 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
Wczytaj plik binarny warstwy weryfikacji z innego pliku APK
Za pomocą adb
możesz zainstalować plik APK zawierający warstwę, a następnie włączyć warstwę.
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 poszczególnych aplikacji trwają do ponownego uruchomienia, a właściwości globalne są czyszczone przy ponownym uruchomieniu.
Włączanie warstw dla poszczególnych aplikacji
Aby włączyć warstwy dla poszczególnych aplikacji:
Użyj ustawień powłoki adb, aby włączyć warstwy:
$ adb shell settings put global enable_gpu_debug_layers 1
Określ aplikację docelową, w której zostaną włączone warstwy:
$ adb shell settings put global gpu_debug_app <package_name>
Określ listę warstw do włączenia (od góry do dołu), oddzielając poszczególne warstwy dwukropkiem:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Ponieważ mamy jedną warstwę walidacji Khronos, polecenie prawdopodobnie będzie wyglądać tak:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
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ą zapamiętywane po ponownym uruchomieniu urządzenia, więc warto je wyczyścić po wczytaniu 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 wszystkich aplikacji, w tym natywnych plików wykonywalnych.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>