Die meisten expliziten Grafik-APIs führen keine Fehlerprüfung durch, da dies zu Leistungseinbußen führen kann. Vulkan verfügt über Validierungsebenen , die während der Entwicklung eine Fehlerprüfung ermöglichen und so Leistungseinbußen im Release-Build Ihrer App vermeiden. Validierungsebenen basieren auf einem allgemeinen Layering-Mechanismus, der API-Einstiegspunkte abfängt.
Einzelne Khronos-Validierungsebene
Bisher stellte Vulkan mehrere Validierungsebenen bereit, die in einer bestimmten Reihenfolge aktiviert werden mussten. Ab dem Vulkan SDK-Release 1.1.106.0 muss Ihre App
nur eine einzige Validierungsebene
aktivieren,
VK_LAYER_KHRONOS_validation, um alle Funktionen der vorherigen
Validierungsebenen zu erhalten.
Im APK verpackte Validierungsebenen verwenden
Wenn Sie Validierungsebenen in Ihr APK packen, wird eine optimale Kompatibilität gewährleistet. Die Validierungsebenen sind als vorgefertigte Binärdateien verfügbar oder können aus Quellcode erstellt werden.
Vorgefertigte Binärdateien verwenden
Laden Sie die neuesten Android Vulkan Validation Layer-Binärdateien von der GitHub Release-Seite herunter.
Die einfachste Möglichkeit, die Ebenen zu Ihrem APK hinzuzufügen, besteht darin, die vorgefertigten Ebenen-Binärdateien in das Verzeichnis src/main/jniLibs/ Ihres Moduls zu extrahieren, wobei die ABI-Verzeichnisse (z. B. arm64-v8a oder x86-64) intakt bleiben. Beispiel:
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
Validierungsebene aus Quellcode erstellen
Wenn Sie den Quellcode der Validierungsebene debuggen möchten, rufen Sie den neuesten Quellcode aus dem GitHub-Repository der Khronos Group ab und folgen Sie der dortigen Anleitung.
Prüfen, ob die Validierungsebene korrekt verpackt ist
Unabhängig davon, ob Sie mit den vorgefertigten Ebenen von Khronos oder mit aus Quellcode erstellten Ebenen arbeiten, wird beim Build-Prozess eine endgültige Dateistruktur in Ihrem APK erstellt, die in etwa so aussieht:
lib/
arm64-v8a/
libVkLayer_khronos_validation.so
armeabi-v7a/
libVkLayer_khronos_validation.so
x86/
libVkLayer_khronos_validation.so
x86-64/
libVkLayer_khronos_validation.so
Der folgende Befehl zeigt, wie Sie prüfen, ob Ihr APK die Validierungsebene wie erwartet enthält:
$ 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
Validierungsebene während der Instanzerstellung aktivieren
Mit der Vulkan API kann eine App Ebenen während der Instanzerstellung aktivieren. Einstiegspunkte, die von einer Ebene abgefangen werden, müssen eines der folgenden Objekte als ersten Parameter haben:
VkInstanceVkPhysicalDeviceVkDeviceVkCommandBufferVkQueue
Rufen Sie vkEnumerateInstanceLayerProperties()
auf, um die verfügbaren Ebenen und ihre Eigenschaften aufzulisten. Vulkan aktiviert Ebenen, wenn
vkCreateInstance()
ausgeführt wird.
Das folgende Code-Snippet zeigt, wie eine App die Vulkan API verwenden kann, um Ebenen programmatisch abzufragen und zu aktivieren:
// 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,
Standard-Logcat-Ausgabe
Die Validierungsebene gibt Warnungen und Fehlermeldungen in Logcat aus, die mit dem Tag VALIDATION gekennzeichnet sind. Eine Meldung der Validierungsebene sieht so aus (Zeilenumbrüche wurden hier zur besseren Lesbarkeit hinzugefügt):
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-Callback aktivieren
Mit der Debug Utils-Erweiterung VK_EXT_debug_utils kann Ihre Anwendung einen Debug-Messenger erstellen, der Meldungen der Validierungsebene an einen von der Anwendung bereitgestellten Callback übergibt. Diese Erweiterung wird möglicherweise nicht von Ihrem Gerät unterstützt, ist aber in den neuesten Validierungsebenen implementiert. Es gibt auch eine verworfene Erweiterung namens VK_EXT_debug_report, die ähnliche Funktionen bietet, wenn VK_EXT_debug_utils nicht verfügbar ist.
Bevor Sie die Debug Utils-Erweiterung verwenden, sollten Sie prüfen, ob sie von Ihrem Gerät oder einer geladenen Validierungsebene unterstützt wird. Das folgende Beispiel zeigt, wie Sie prüfen, ob die Debug Utils-Erweiterung unterstützt wird, und einen Callback registrieren, wenn die Erweiterung entweder vom Gerät oder von der Validierungsebene unterstützt wird.
// 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); }
Nachdem Ihre App den Callback registriert und aktiviert hat, leitet das System Debugging-Meldungen an ihn weiter.
#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; }
Externe Validierungsebenen verwenden
Sie müssen Validierungsebenen nicht in Ihr APK packen. Geräte mit Android 9 (API-Level 28) und höher können Validierungsebenen verwenden, die sich außerhalb Ihrer Binärdatei befinden, und sie dynamisch aktivieren und deaktivieren. Führen Sie die Schritte in diesem Abschnitt aus, um Validierungsebenen auf Ihr Testgerät zu übertragen:
App für die Verwendung externer Validierungsebenen aktivieren
Das Sicherheitsmodell und die Richtlinien von Android unterscheiden sich erheblich von denen anderer Plattformen. Damit externe Validierungsebenen geladen werden können, muss eine der folgenden Bedingungen erfüllt sein:
Die Ziel-App ist debugfähig. Diese Option führt zu mehr Debug-Informationen, kann sich aber negativ auf die Leistung Ihrer App auswirken.
Die Ziel-App wird in einem Userdebug-Build des Betriebssystems ausgeführt, der Root-Zugriff gewährt.
Nur für Apps, die auf Android 11 (API-Level 30) oder höher ausgerichtet sind: Die Android Manifestdatei des Ziels enthält das folgende
meta-dataElement:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Externe Validierungsebene laden
Auf Geräten mit Android 9 (API-Level 28) und höher kann Vulkan die Validierungsebene aus dem lokalen Speicher Ihrer App laden. Ab Android 10 (API-Level 29) kann Vulkan die Validierungsebene auch aus einem separaten APK laden. Sie können eine beliebige Methode auswählen, solange sie von Ihrer Android-Version unterstützt wird.
Binärdatei der Validierungsebene aus dem lokalen Speicher des Geräts laden
Da Vulkan die Binärdatei im temporären Datenspeicher verzeichnis Ihres Geräts sucht, müssen Sie die Binärdatei zuerst mit der Android Debug Bridge (adb) in dieses Verzeichnis übertragen. So gehts:
Verwenden Sie den
adb pushBefehl, um die Ebenen-Binärdatei in den Datenspeicher Ihrer App auf dem Gerät zu laden:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Verwenden Sie die
adb shellundrun-asBefehle, um die Ebene über den App-Prozess zu laden. Das bedeutet, dass die Binärdatei denselben Gerätezugriff hat wie die App, ohne dass Root-Zugriff erforderlich ist.$ 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
Binärdatei der Validierungsebene aus einem anderen APK laden
Sie können adb verwenden, um ein APK zu installieren, das
die Ebene enthält, und dann die Ebene aktivieren.
adb install --abi abi path_to_apk
Ebenen außerhalb der Anwendung aktivieren
Sie können Vulkan-Ebenen entweder pro App oder global aktivieren. Die Einstellungen pro App bleiben auch nach einem Neustart erhalten, während globale Eigenschaften beim Neustart gelöscht werden.
Ebenen pro App aktivieren
So aktivieren Sie Ebenen pro App:
Verwenden Sie die adb-Shell-Einstellungen, um die Ebenen zu aktivieren:
$ adb shell settings put global enable_gpu_debug_layers 1Geben Sie die Zielanwendung an, für die die Ebenen aktiviert werden sollen:
$ adb shell settings put global gpu_debug_app <package_name>
Geben Sie die Liste der zu aktivierenden Ebenen an (von oben nach unten) und trennen Sie die einzelnen Ebenen durch einen Doppelpunkt:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Da wir eine einzelne Khronos-Validierungsebene haben, sieht der Befehl wahrscheinlich so aus:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validationGeben Sie ein oder mehrere Pakete an, in denen nach Ebenen gesucht werden soll:
$ adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
Mit den folgenden Befehlen können Sie prüfen, ob die Einstellungen aktiviert sind:
$ 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
Da die von Ihnen angewendeten Einstellungen auch nach einem Neustart des Geräts erhalten bleiben, sollten Sie die Einstellungen löschen, nachdem die Ebenen geladen wurden:
$ 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
Ebenen global aktivieren
Sie können eine oder mehrere Ebenen global bis zum nächsten Neustart aktivieren. Dabei wird versucht, die Ebenen für alle Anwendungen zu laden, einschließlich nativer ausführbarer Dateien.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>