La mayoría de las API de gráficos explícitos no realizan comprobación de errores, porque hacerlo puede generar una penalidad de rendimiento. Vulkan proporciona comprobación de errores de modo que puedas usar esta función durante el desarrollo y excluirla de la compilación de actualización de la app, lo que evita la penalidad cuando resultaría más perjudicial. Para eso, habilita la capa de validación de Vulkan. Esta capa de validación intercepta o atrapa los puntos de entrada de Vulkan para diversos propósitos de depuración y validación.
La capa de validación intercepta los puntos de entrada para los que contiene definiciones. Un punto de entrada que no se definió en la capa llega al controlador, el nivel de base, sin validación.
Los ejemplos del NDK de Android y Vulkan incluyen la capa de validación de Vulkan para usar durante el desarrollo. Puedes incluir la capa de validación en la pila de gráficos, lo que le permite informar errores de validación. Esta instrumentación te brinda la posibilidad de detectar y solucionar usos inadecuados durante el desarrollo.
La capa de validación única de Khronos
El cargador puede insertar capas de Vulkan en una pila de modo que las de mayor nivel llamen a la capa inferior y la pila de capas finalice en el controlador del dispositivo. En el pasado, varias capas de validación se habilitaban en un orden específico en Android. Sin embargo, ahora hay una sola capa, VK_LAYER_KHRONOS_validation
, que abarca todo el comportamiento de las capas de validación anteriores. Para la validación de Vulkan, todas las apps deben habilitar la capa de validación única, VK_LAYER_KHRONOS_validation
.
Cómo empaquetar la capa de validación
El NDK incluye un objeto binario de capa de validación compilado previamente que puedes enviar al dispositivo de prueba, empaquetándolo en el APK. Encontrarás este objeto binario en el siguiente directorio:ndk-dir/sources/third_party/vulkan/src/build-android/jniLibs/abi/
. Cuando la app lo solicita, el cargador Vulkan busca y carga la capa desde el APK de la app.
Cómo empaquetar la capa de validación en tu APK con Gradle
Puedes agregar la capa de validación al proyecto mediante el complemento de Gradle para Android y la compatibilidad de Android Studio con CMake y ndk-build. Para incorporar las bibliotecas usando la compatibilidad de Android Studio con CMake y ndk-build, agrega lo siguiente al archivobuild.gradle
del módulo de tu app:
sourceSets { main { jniLibs { // Gradle includes libraries in the following path as dependencies // of your CMake or ndk-build project so that they are packaged in // your app’s APK. srcDir "ndk-path/sources/third_party/vulkan/src/build-android/jniLibs" } } }Para obtener más información sobre la compatibilidad de Android Studio con CMake y ndk-build, consulta Cómo agregar código C y C++ a tu proyecto.
Cómo empaquetar la capa de validación en bibliotecas JNI
Puedes agregar manualmente los objetos binarios de la capa de validación al directorio de bibliotecas JNI de tu proyecto usando las siguientes opciones de líneas de comandos:$ cd project-root $ mkdir -p app/src/main $ cp -fr ndk-path/sources/third_party/vulkan/src/build-android/jniLibs app/src/main/
Cómo compilar objetos binarios de capas desde un código fuente
Si tu app necesita la última capa de validación, puedes obtener el código fuente más reciente desde el repositorio de GitHub de Khronos Group y seguir las instrucciones de compilación que allí se detallan.
Cómo verificar la compilación de las capas
Independientemente de que realices compilaciones con capas del NDK compiladas previamente o a partir del código fuente más reciente, el proceso de compilación genera una estructura de archivo final como la siguiente:
src/main/jniLibs/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so
En el siguiente ejemplo, se muestra la manera de verificar que tu APK contenga las capas de validación según lo previsto:
$ jar -xvf project.apk ... inflated: lib/arm64-v8a/libVkLayer_khronos_validation.so ...
Cómo habilitar las capas
La API de Vulkan permite que una app habilite capas. Estas se habilitan durante la creación de instancias. Los puntos de entrada que intercepta una capa deben tener como primer parámetro alguno de estos objetos:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
Puedes llamar a vkEnumerateInstanceLayerProperties()
para que enumere las capas disponibles y sus propiedades. El sistema habilita las capas cuando se ejecuta vkCreateInstance()
.
En el siguiente fragmento de código, se muestra la manera en que una app puede usar la API de Vulkan para habilitar y consultar una capa de forma programática:
// Get layer count using null pointer as last parameter uint32_t instance_layer_present_count = 0; vkEnumerateInstanceLayerProperties(&instance_layer_present_count, nullptr); // Enumerate layers with valid pointer in last parameter VkLayerProperties* layer_props = (VkLayerProperties*)malloc(instance_layer_present_count * sizeof(VkLayerProperties)); vkEnumerateInstanceLayerProperties(&instance_layer_present_count, layer_props)); // Make sure the desired validation layer is available const char *instance_layers[] = { "VK_LAYER_KHRONOS_validation" }; uint32_t instance_layer_request_count = sizeof(instance_layers) / sizeof(instance_layers[0]); for (uint32_t i = 0; i < instance_layer_request_count; i++) { bool found = false; for (uint32_t j = 0; j < instance_layer_present_count; j++) { if (strcmp(instance_layers[i], layer_props[j].layerName) == 0) { found = true; } } if (!found) { error(); } } // Pass desired layer into vkCreateInstance VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.enabledLayerCount = instance_layer_request_count; instance_info.ppEnabledLayerNames = instance_layers; ...
Salida predeterminada de logcat
La capa de validación emite mensajes de advertencia y error en logcat, etiquetados con una etiquetaVALIDATION
. Un mensaje de la capa de validación se ve como lo siguiente:
VALIDATION: UNASSIGNED-CoreValidation-DrawState-QueueForwardProgress(ERROR / SPEC): msgNum: 0 - VkQueue 0x7714c92dc0[] is waiting on VkSemaphore 0x192e[] that has no way to be signaled. VALIDATION: Objects: 1 VALIDATION: [0] 0x192e, type: 5, name: NULL
Cómo habilitar la devolución de llamada de depuración
La extensión VK_EXT_debug_utils
de Debug Utils permite que tu app cree un mensajero de depuración que pasará los mensajes de la capa de validación a una devolución de llamada proporcionada por la aplicación. Ten en cuenta que también hay una extensión obsoleta, VK_EXT_debug_report
, que proporciona una capacidad similar si VK_EXT_debug_utils
no está disponible.
Antes de usar la extensión de Debug Utils, debes asegurarte de que sea compatible con la plataforma. En el siguiente ejemplo, se muestra la manera de comprobar la compatibilidad con la extensión de depuración y de registrar una devolución de llamada si se admite la extensión.
// Get the instance extension count uint32_t inst_ext_count = 0; vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, nullptr); // Enumerate the instance extensions VkExtensionProperties* inst_exts = (VkExtensionProperties *)malloc(inst_ext_count * sizeof(VkExtensionProperties)); vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, inst_exts); const char * enabled_inst_exts[16] = {}; uint32_t enabled_inst_ext_count = 0; // Make sure the debug utils extension is available for (uint32_t i = 0; i < inst_ext_count; i++) { if (strcmp(inst_exts[i].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { enabled_inst_exts[enabled_inst_ext_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; } } if (enabled_inst_ext_count == 0) return; // Pass the instance extensions into vkCreateInstance VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.enabledExtensionCount = enabled_inst_ext_count; instance_info.ppEnabledExtensionNames = enabled_inst_exts; PFN_vkCreateDebugUtilsMessengerEXT pfnCreateDebugUtilsMessengerEXT; PFN_vkDestroyDebugUtilsMessengerEXT pfnDestroyDebugUtilsMessengerEXT; pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetDeviceProcAddr(device, "vkCreateDebugUtilsMessengerEXT"); pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetDeviceProcAddr(device, "vkDestroyDebugUtilsMessengerEXT"); assert(pfnCreateDebugUtilsMessengerEXT); assert(pfnDestroyDebugUtilsMessengerEXT); // Create the debug messenger callback with desired settings 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; messengerInfo.pfnUserCallback = &DebugUtilsMessenger; // Callback example below 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); }
Una vez que la app registra y habilita la devolución de llamada de depuración, el sistema enruta los mensajes de depuración a una devolución de llamada registrada. El siguiente es un ejemplo de una devolución de llamada como esta:
#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; }
Cómo enviar capas al dispositivo de prueba mediante ADB
Sigue los pasos de esta sección para enviar capas a tu dispositivo de prueba:
Cómo habilitar la depuración
El modelo de seguridad y las políticas de Android difieren considerablemente de los de otras plataformas. Para cargar capas externas, una de las siguientes opciones debe ser verdadera:
- El archivo de manifiesto de la app de destino incluye el siguiente elemento de metadatos (solo se aplica a apps que se orientan a Android 11 [nivel de API 30]) o versiones posteriores):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
Debes usar esta opción para generar el perfil de tu aplicación. - La app de destino es depurable. Esta opción te brinda más información de depuración, pero puede afectar negativamente el rendimiento de la app.
- La app de destino se ejecuta en una compilación userdebug del sistema operativo que otorga acceso de raíz.
Cómo cargar las capas
Los dispositivos que ejecutan Android 9 (nivel de API 28) y versiones posteriores permiten que Vulkan cargue capas desde el almacenamiento local de la app. Android 10 (nivel de API 29) admite la carga de capas desde un APK independiente.
- Objetos binarios de capas en el almacenamiento local del dispositivo
- Un APK que contiene las capas, instalado por separado de la aplicación de destino
Objetos binarios de capas en el almacenamiento local del dispositivo
Vulkan busca los objetos binarios en el directorio de almacenamiento de datos temporales del dispositivo, así que primero debes enviar los objetos binarios a ese directorio mediante Android Debug Bridge (ADB). Sigue estos pasos:
-
Usa el comando
adb push
para cargar los objetos binarios de capas que quieras en el almacenamiento de datos de la app en el dispositivo. En el siguiente ejemplo, se envíalibVkLayer_khronos_validation.so
al directorio/data/local/tmp
del dispositivo:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
-
Usa los comandos
adb shell
yrun-as
para cargar las capas a través del proceso de tu app. Esto implica que los objetos binarios tienen el mismo acceso al dispositivo que la aplicación, pero no se requiere acceso al directorio raíz.$ 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
- Habilita las capas
APK que contiene las capas
Puedes usar adb
para instalar el APK y, luego, habilitar las capas.
adb install --abi abi path_to_apk
Cómo habilitar las capas fuera de la aplicación
Puedes habilitar las capas por app o de forma global. La configuración por app persiste después de los reinicios, mientras que las propiedades globales se borran.
Para habilitar las capas por app, haz lo siguiente:
# Enable layers adb shell settings put global enable_gpu_debug_layers 1 # Specify target application adb shell settings put global gpu_debug_app <package_name> # Specify layer list (from top to bottom) adb shell settings put global gpu_debug_layers <layer1:layer2:layerN> # Specify packages to search for layers adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
Para comprobar si la configuración está habilitada, usa los siguientes comandos:
$ 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
Dado que los ajustes que aplicas persisten luego de todos los reinicios del dispositivo, puedes borrarlos una vez que las capas se carguen:
$ 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
Para inhabilitar las capas por app, haz lo siguiente:
# Delete the global setting that enables layers adb shell settings delete global enable_gpu_debug_layers # Delete the global setting that selects target application adb shell settings delete global gpu_debug_app # Delete the global setting that specifies layer list adb shell settings delete global gpu_debug_layers # Delete the global setting that specifies layer packages adb shell settings delete global gpu_debug_layer_app
Para habilitar las capas globalmente, haz lo siguiente:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>