Większość interfejsów API do obsługi grafiki nie wykonuje sprawdzania błędów, ponieważ może to obniżyć wydajność. Vulkan ma warstwy weryfikacji, które zapewniają sprawdzanie błędów podczas tworzenia, zapobiegając spadkowi wydajności w wersji aplikacji przeznaczonej do opublikowania. Warstwy weryfikacji korzystają z uniwersalnego mechanizmu warstw, który przechwytuje punkty wejścia interfejsu API.
Jedna warstwa walidacji Khronos
Wcześniej Vulkan udostępniał wiele warstw weryfikacji, które trzeba było włączyć w określonej kolejności. Począwszy od wersji pakietu Vulkan SDK 1.1.106.0, aby uzyskać dostęp do wszystkich funkcji z poprzednich warstw weryfikacji, wystarczy, że w aplikacji będzie włączona pojedyncza warstwa weryfikacji (VK_LAYER_KHRONOS_validation
).
Używanie warstw weryfikacji zapakowanych w pliku APK
Warstwy weryfikacji w pakiecie APK zapewniają optymalną zgodność. Warstwy weryfikacji są dostępne jako wstępnie skompilowane pliki binarne lub można je skompilować z kodu źródłowego.
Używanie gotowych plików binarnych
Pobierz najnowsze binarne pliki obsługi weryfikacji Vulkana na Androida ze strony wersji GitHub.
Najprostszym sposobem dodania warstw do pliku APK jest wyodrębnienie gotowych binarnych plików binarnych warstw do katalogu src/main/jniLibs/
w module, zachowując przy tym katalogi ABI (takie jak arm64-v8a
lub x86-64
), np. w ten sposób:
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 debugować kod źródłowy warstwy weryfikacji, pobierz najnowszą wersję źródłową z repozytorium GitHub grupy Kronos i postępuj zgodnie z instrukcjami kompilacji.
Sprawdź, czy warstwa weryfikacji jest prawidłowo spakowana
Niezależnie od tego, czy kompilujesz z użyciem wstępnie utworzonych warstw Khronos, czy z użyciem warstw utworzonych na podstawie kodu źródłowego, proces kompilacji wygeneruje w pliku APK ostateczną strukturę pliku w postaci:
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ższe polecenie pokazuje, 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
Aby wyświetlić listę dostępnych warstw i ich właściwości, wywołaj funkcję vkEnumerateInstanceLayerProperties()
. Vulkan włącza warstwy podczas wykonywania vkCreateInstance()
.
Ten fragment kodu pokazuje, jak aplikacja może używać interfejsu Vulkan API do programowego zapytania i włączenia 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 emituje komunikaty ostrzeżenia i błędów w logcat z oznaczeniem VALIDATION
. Komunikat poziomu weryfikacji wygląda tak (w celu ułatwienia przewijania dodano tu podziały wierszy):
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łączanie wywołania zwrotnego debugowania
Rozszerzenie Debug Utils VK_EXT_debug_utils
umożliwia aplikacji tworzenie
komunikatora debugowania, który przekazuje komunikaty poziomu weryfikacji do wywołania zwrotnego dostarczonego przez aplikację. Twoje urządzenie może nie implementować tego rozszerzenia, ale jest ono stosowane w najnowszych warstwach weryfikacji. Dostępne jest też wycofane rozszerzenie o nazwie VK_EXT_debug_report
, które zapewnia podobne funkcje, jeśli VK_EXT_debug_utils
jest niedostępne.
Zanim użyjesz rozszerzenia Debug Utils, upewnij się, że Twoje urządzenie lub załadowana warstwa weryfikacji je obsługuje. Ten przykład pokazuje, jak sprawdzić, czy rozszerzenie debugowania jest obsługiwane, i zarejestrować 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 zarejestruje i włączy wywołanie zwrotne, system będzie kierować do niej wiadomości 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; }
Używanie warstw weryfikacji zewnętrznej
Nie musisz pakować warstw weryfikacji w pliku APK. Urządzenia z Androidem 9 (poziom interfejsu API 28) lub nowszym mogą używać warstw weryfikacji zewnętrznych względem binarnych i dynamicznie je włączać i wyłączać. Aby przesłać warstwy weryfikacji na urządzenie testowe, wykonaj czynności opisane w tej sekcji:
Włączanie w aplikacji zewnętrznych warstw weryfikacji
Model i zasady bezpieczeństwa Androida różnią się znacznie od innych platform. Aby załadować zewnętrzne warstwy weryfikacji, musi być spełniony jeden z tych warunków:
Aplikacja docelowa jest debugowalna. Ta opcja zapewnia więcej informacji o debugowaniu, ale może negatywnie wpływać na wydajność aplikacji.
Aplikacja docelowa jest uruchamiana na wersji userdebug systemu operacyjnego, która przyznaje dostęp root.
Aplikacje kierowane tylko na Androida 11 (poziom API 30) lub nowszego: plik manifestu Androida zawiera element
meta-data
:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Ładowanie zewnętrznej warstwy walidacji
Urządzenia z Androidem 9 (poziom interfejsu API 28) lub nowszym umożliwiają Vulkanowi załadowanie warstwy walidacji z pamięci lokalnej aplikacji. Od Androida 10 (poziom interfejsu API 29) Vulkan może też wczytywać warstwę weryfikacji z osobnego pliku APK. Możesz wybrać dowolną metodę, o ile jest ona obsługiwana przez Twoją wersję Androida.
wczytać binarne dane poziomu weryfikacji z pamięci lokalnej urządzenia;
Vulkan szuka pliku binarnego w tymczasowym katalogu danych urządzenia, więc musisz najpierw przesłać plik binarny do tego katalogu za pomocą Android Debug Bridge (adb) w ten sposób:
Użyj polecenia
adb push
, aby załadować binarny plik warstwy do pamięci danych aplikacji na urządzeniu:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Aby załadować warstwę w ramach procesu aplikacji, użyj poleceń
adb shell
irun-as
. Oznacza to, że plik binarny ma taki sam dostęp do urządzenia jak aplikacja, bez konieczności uzyskiwania dostępu do 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
Ładowanie binarnego pliku binarnego warstwy weryfikacji z innego pliku APK
Za pomocą adb
możesz zainstalować plik APK, który zawiera warstwę, a następnie włączyć tę warstwę.
adb install --abi abi path_to_apk
Włączanie warstw poza aplikacją
Warstwy Vulkan możesz włączyć na poziomie aplikacji lub globalnie. Ustawienia poszczególnych aplikacji pozostają po ponownym uruchomieniu, podczas gdy właściwości globalne są czyszczone po ponownym uruchomieniu.
Włączanie warstw na poziomie aplikacji
Aby włączyć warstwy w poszczególnych aplikacjach:
Aby włączyć warstwy, użyj ustawień adb shell:
$ adb shell settings put global enable_gpu_debug_layers 1
Określ aplikację docelową, aby włączyć warstwy:
$ adb shell settings put global gpu_debug_app <package_name>
Podaj listę warstw do włączenia (od góry do dołu), oddzielając każdą z nich dwukropkiem:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Ponieważ mamy jedną warstwę weryfikacji Khronos, polecenie będzie wyglądać tak:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
Określ co najmniej 1 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
Ustawienia, które zastosujesz, są zachowywane po ponownym uruchomieniu urządzenia, dlatego warto je usunąć 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łączanie warstw globalnie
Możesz włączyć jedną lub więcej warstw globalnie do następnego ponownego uruchomienia. Spowoduje to próbę załadowania warstw dla wszystkich aplikacji, w tym plików wykonywalnych natywnych.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>