Większość interfejsów API grafiki nie sprawdza błędów, ponieważ może to wpłynąć na wydajność. Vulkan ma warstwy weryfikacji, które sprawdzają błędy podczas programowania, dzięki czemu nie musisz ponosić kosztów związanych z wydajnością w wersji aplikacji przeznaczonej do publikacji. Warstwy weryfikacji korzystają z ogólnego mechanizmu warstwowego, który przechwytuje punkty wejścia interfejsu API.
Pojedyncza warstwa weryfikacji Khronos
Wcześniej Vulkan udostępniał wiele warstw weryfikacji, które należało włączyć w określonej kolejności. Od wersji 1.1.106.0 pakietu SDK Vulkan aplikacja musi włączyć tylko jedną warstwę weryfikacjiVK_LAYER_KHRONOS_validation
, aby uzyskać wszystkie funkcje z poprzednich warstw weryfikacji.
Używanie warstw weryfikacji spakowanych w pliku APK
Warstwy weryfikacji pakietu w pliku APK zapewniają optymalną zgodność. Warstwy weryfikacji są dostępne jako gotowe pliki binarne lub można je skompilować z kodu źródłowego.
Używanie gotowych plików binarnych
Pobierz najnowsze pliki binarne warstwy weryfikacji interfejsu Vulkan na Androida ze strony z wersjami w GitHub.
Najprostszym sposobem dodania warstw do pliku APK jest wyodrębnienie wstępnie skompilowanych plików binarnych warstw do katalogu src/main/jniLibs/
modułu z zachowaniem katalogów ABI (np. arm64-v8a
lub x86-64
), jak w tym przykładzie:
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 najnowsze źródło z repozytorium GitHub grupy Khronos i postępuj zgodnie z instrukcjami kompilacji.
Sprawdź, czy warstwa weryfikacji została prawidłowo spakowana.
Niezależnie od tego, czy używasz gotowych warstw Khronos, czy warstw utworzonych ze źródeł, proces kompilacji tworzy w pliku APK strukturę plików podobną do tej:
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:
$ 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. Vulkan włącza warstwy, gdy wykonywana jest funkcja
vkCreateInstance()
.
Ten fragment kodu pokazuje, jak aplikacja może używać interfejsu Vulkan API do programowego wysyłania zapytań o warstwy i ich włączania:
// 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 o ostrzeżeniach i błędach w logcat oznaczone tagiem VALIDATION
. Komunikat warstwy weryfikacji wygląda tak (znaki końca wiersza zostały dodane, 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łączanie wywołania zwrotnego debugowania
Rozszerzenie Debug Utils VK_EXT_debug_utils
umożliwia aplikacji utworzenie posłańca debugowania, który przekazuje komunikaty warstwy weryfikacji do wywołania zwrotnego dostarczonego przez aplikację. Urządzenie może nie obsługiwać 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, jeśli rozszerzenie VK_EXT_debug_utils
jest niedostępne.
Zanim zaczniesz korzystać z rozszerzenia Debug Utils, upewnij się, że Twoje urządzenie lub załadowana warstwa weryfikacji je obsługuje. Poniższy przykład pokazuje, jak sprawdzić, czy rozszerzenie narzędzi do debugowania jest obsługiwane, i zarejestrować wywołanie zwrotne, jeśli rozszerzenie jest obsługiwane przez urządzenie lub warstwę weryfikacji.
// 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 do niej kierować 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; }
Używanie zewnętrznych warstw weryfikacji
Nie musisz pakować warstw weryfikacji w pliku APK. Urządzenia z Androidem 9 (poziom interfejsu API 28) i nowszym mogą używać warstw weryfikacji zewnętrznych w stosunku do pliku binarnego oraz 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 możliwości korzystania z zewnętrznych warstw weryfikacji
Model zabezpieczeń i zasady Androida znacznie różnią się od innych platform. Aby wczytać zewnętrzne warstwy weryfikacji, musi być spełniony jeden z tych warunków:
Aplikacja docelowa jest debugowalna. Ta opcja zapewnia więcej informacji do debugowania, ale może negatywnie wpłynąć na wydajność aplikacji.
Aplikacja docelowa jest uruchamiana w wersji userdebug systemu operacyjnego, która przyznaje dostęp do roota.
Dotyczy tylko aplikacji kierowanych na Androida 11 (poziom API 30) lub nowszego: docelowy plik manifestu Androida zawiera element
meta-data
:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Wczytywanie zewnętrznej warstwy weryfikacji
Urządzenia z Androidem 9 (poziom API 28) i nowszym umożliwiają interfejsowi Vulkan wczytywanie warstwy weryfikacji z pamięci lokalnej aplikacji. Od Androida 10 (API na poziomie 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.
Wczytywanie binarnego pliku warstwy weryfikacyjnej z pamięci lokalnej urządzenia
Interfejs Vulkan szuka pliku binarnego w katalogu tymczasowego przechowywania danych na urządzeniu, dlatego 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 wczytać binarny plik warstwy do pamięci danych aplikacji na urządzeniu:$ 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 jak aplikacja, ale nie wymaga 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
Wczytywanie pliku binarnego warstwy weryfikacji z innego pliku APK
Możesz użyć adb
, aby zainstalować plik APK zawierający 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ć dla poszczególnych aplikacji lub globalnie. Ustawienia poszczególnych aplikacji są zachowywane po ponownym uruchomieniu, a właściwości globalne są czyszczone po ponownym uruchomieniu.
Włączanie warstw w poszczególnych aplikacjach
Poniższe kroki opisują, jak włączyć warstwy w poszczególnych aplikacjach:
Aby włączyć warstwy, użyj ustawień powłoki adb:
$ adb shell settings put global enable_gpu_debug_layers 1
Określ aplikację docelową, w której chcesz 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), rozdzielając poszczególne warstwy dwukropkiem:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Ponieważ mamy jedną warstwę weryfikacji Khronos, polecenie będzie prawdopodobnie 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>
Możesz sprawdzić, czy ustawienia są włączone, za pomocą 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
Ponieważ zastosowane ustawienia są zachowywane po ponownym uruchomieniu urządzenia, po wczytaniu warstw możesz je wyczyścić:
$ 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. Próbuje wczytać warstwy dla wszystkich aplikacji, w tym natywnych plików wykonywalnych.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>