La maggior parte delle API di grafica esplicita non esegue il controllo degli errori perché ciò può comportare un calo delle prestazioni. Vulkan dispone di livelli di convalida che forniscono il controllo degli errori durante lo sviluppo, evitando il calo delle prestazioni nella build di rilascio dell'app. I livelli di convalida si basano su un meccanismo di stratificazione generico che intercetta i punti di contatto dell'API.
Un unico livello di convalida Khronos
In precedenza, Vulkan forniva più livelli di convalida che dovevano essere attivati
in un ordine specifico. A partire dalla versione 1.1.106.0 dell'SDK Vulkan, la tua app deve attivare solo un unico livello di convalida,VK_LAYER_KHRONOS_validation
, per ottenere tutte le funzionalità dei livelli di convalida precedenti.
Utilizzare i livelli di convalida pacchettizzati nell'APK
I livelli di convalida del pacchettizzazione all'interno dell'APK garantiscono una compatibilità ottimale. I livelli di convalida sono disponibili come binari precompilati o possono essere compilati dal codice sorgente.
Utilizzare i binari precompilati
Scarica i binari più recenti del livello di convalida Vulkan per Android dalla pagina di release di GitHub.
Il modo più semplice per aggiungere i livelli all'APK è estrarre i binari precompilati dei livelli nella directory src/main/jniLibs/
del modulo, con le directory ABI (ad esempio arm64-v8a
o x86-64
) intatte, come segue:
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
Crea il livello di convalida dal codice sorgente
Per eseguire il debug nel codice sorgente del livello di convalida, recupera l'ultima versione del codice sorgente dal repository GitHub del gruppo Khronos e segui le istruzioni di compilazione.
Verifica che il livello di convalida sia pacchettizzato correttamente
Indipendentemente dal fatto che tu esegui la compilazione con i livelli precompilati di Khronos o con i livelli compilati da sorgente, il processo di compilazione produce una struttura di file finale nell'APK come la seguente:
lib/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so x86/ libVkLayer_khronos_validation.so x86-64/ libVkLayer_khronos_validation.so
Il seguente comando mostra come verificare che l'APK contenga il livello di convalida come previsto:
$ 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
Attivare un livello di convalida durante la creazione dell'istanza
L'API Vulkan consente a un'app di attivare i livelli durante la creazione dell'istanza. I punti di entrata intercettati da un livello devono avere uno dei seguenti oggetti come primo parametro:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
Chiama vkEnumerateInstanceLayerProperties()
per elencare i livelli disponibili e le relative proprietà. Vulkan attiva i livelli quando viene eseguito vkCreateInstance()
.
Il seguente snippet di codice mostra come un'app può utilizzare l'API Vulkan per eseguire query e attivare i livelli in modo programmatico:
// 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,
Output logcat predefinito
Il livello di convalida emette messaggi di avviso ed errore in logcat etichettati con un
VALIDATION
tag. Un messaggio del livello di convalida è simile al seguente (con le interruzioni di riga aggiunte per facilitare lo scorrimento):
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)
Attiva il callback di debug
L'estensione Debug Utils VK_EXT_debug_utils
consente alla tua applicazione di creare un messaggio di debug che trasmette i messaggi del livello di convalida a un callback fornito dall'applicazione. Il tuo dispositivo potrebbe non implementare questa estensione, ma è implementata nei livelli di convalida più recenti. Esiste anche un'estensione ritirata chiamata
VK_EXT_debug_report
, che offre funzionalità simili se
VK_EXT_debug_utils
non è disponibile.
Prima di utilizzare l'estensione Debug Utils, devi assicurarti che il tuo dispositivo o un livello di convalida caricato la supporti. L'esempio seguente mostra come verificare se l'estensione utils di debug è supportata e registrare un callback se l'estensione è supportata dal dispositivo o dal livello di convalida.
// 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); }
Dopo che l'app ha registrato e attivato il callout, il sistema inoltra i messaggi di debugging.
#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; }
Utilizza livelli di convalida esterni
Non devi pacchettizzare i livelli di convalida nell'APK. I dispositivi con Android 9 (livello API 28) e versioni successive possono utilizzare livelli di convalida esterni al file binario e disattivarli e attivarli dinamicamente. Segui i passaggi descritti in questa sezione per inviare i livelli di convalida al dispositivo di test:
Consentire all'app di utilizzare livelli di convalida esterni
Il modello e i criteri di sicurezza di Android sono molto diversi da quelli di altre piattaforme. Per caricare i livelli di convalida esterni, deve essere vera una delle seguenti condizioni:
L'app di destinazione sia debuggabile. Questa opzione genera più informazioni di debug, ma potrebbe influire negativamente sul rendimento dell'app.
L'app di destinazione viene eseguita su una build userdebug del sistema operativo che concede l'accesso come utente root.
App che hanno come target solo Android 11 (livello API 30) o versioni successive: il file manifest Android di destinazione include il seguente elemento
meta-data
:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Carica un livello di convalida esterno
I dispositivi con Android 9 (livello API 28) e versioni successive consentono a Vulkan di caricare il livello di convalida dallo spazio di archiviazione locale dell'app. A partire da Android 10 (livello API 29), Vulkan può caricare il livello di convalida anche da un APK distinto. Puoi scegliere il metodo che preferisci se la tua versione di Android lo supporta.
Carica un file binario del livello di convalida dallo spazio di archiviazione locale del dispositivo
Poiché Vulkan cerca il file binario nella directory di archiviazione temporanea dei dati del dispositivo, devi prima spingerlo in quella directory utilizzando Android Debug Bridge (adb), come segue:
Utilizza il comando
adb push
per caricare il codice binario del livello nello spazio di archiviazione dei dati dell'app sul dispositivo:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Utilizza i comandi
adb shell
erun-as
per caricare il livello tramite il processo dell'app. In altre parole, il file binario ha lo stesso accesso al dispositivo dell'app senza richiedere l'accesso come utente root.$ 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
Carica un file binario del livello di convalida da un altro APK
Puoi utilizzare adb
per installare un APK che contiene il livello e poi attivarlo.
adb install --abi abi path_to_apk
Attivare i livelli al di fuori dell'applicazione
Puoi attivare i livelli Vulkan per app o a livello globale. Le impostazioni per app persistono dopo i riavvii, mentre le proprietà globali vengono cancellate al riavvio.
Attivare i livelli in base all'app
I passaggi riportati di seguito descrivono come attivare i livelli in base all'app:
Utilizza le impostazioni adb shell per attivare i livelli:
$ adb shell settings put global enable_gpu_debug_layers 1
Specifica l'applicazione di destinazione su cui attivare i livelli:
$ adb shell settings put global gpu_debug_app <package_name>
Specifica l'elenco dei livelli da attivare (dall'alto verso il basso), separando ciascun livello con due punti:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Poiché abbiamo un unico livello di convalida Khronos, il comando sarà probabilmente simile al seguente:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
Specifica uno o più pacchetti in cui cercare i livelli:
$ adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
Puoi verificare se le impostazioni sono abilitate utilizzando i seguenti comandi:
$ 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
Poiché le impostazioni applicate rimangono invariate dopo i riavvii del dispositivo, ti consigliamo di cancellarle dopo il caricamento dei livelli:
$ 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
Attivare i livelli a livello globale
Puoi attivare uno o più livelli a livello globale fino al riavvio successivo. Viene tentato di caricare i livelli per tutte le applicazioni, inclusi gli executable nativi.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>