A maioria das APIs gráficas explícitas não faz a verificação de erros, porque isso pode resultar em uma queda de performance. A Vulkan tem camadas de validação que oferecem verificação de erros durante o desenvolvimento, evitando a queda de performance no build de lançamento do app. As camadas de validação dependem de um mecanismo de camadas de uso geral que intercepta os pontos de entrada da API.
Camada de validação única do Khronos
Antes, a Vulkan fornecia várias camadas de validação que precisavam ser ativadas
em uma ordem específica. Na versão 1.1.106.0 e mais recentes do SDK da Vulkan, seu app
só precisa ativar uma camada de
validação única (link em inglês),
a VK_LAYER_KHRONOS_validation
, para poder usar todos os recursos das camadas de validação
anteriores.
Usar camadas de validação empacotadas no APK
O empacotamento de camadas de validação no APK garante compatibilidade ideal. As camadas estão disponíveis como binários pré-criados ou podem ser criadas usando o código-fonte.
Usar binários pré-criados
Faça o download dos binários mais recentes da camada de validação da Vulkan no Android na página de versão do GitHub (link em inglês).
A maneira mais fácil de adicionar as camadas ao seu APK é extrair os binários
de camada pré-criados para o diretório src/main/jniLibs/
do módulo, com os diretórios de ABI
(como arm64-v8a
ou x86-64
) intactos, desta forma:
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
Criar a camada de validação usando o código-fonte
Para depurar o código-fonte para a camada de validação, extraia a fonte mais recente do repositório GitHub (link em inglês) do Khronos Group e siga as instruções de criação.
Verificar se a camada de validação está empacotada corretamente
Independentemente de você usar camadas pré-criadas do Khronos ou camadas criadas usando o código-fonte, o processo de compilação produzirá uma estrutura de arquivo final no APK semelhante a esta:
lib/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so x86/ libVkLayer_khronos_validation.so x86-64/ libVkLayer_khronos_validation.so
O comando a seguir mostra como verificar se o APK contém a camada de validação esperada:
$ 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
Ativar uma camada de validação durante a criação da instância
A API Vulkan permite que um app ative camadas durante a criação de instâncias. O primeiro parâmetro dos pontos de entrada que uma camada intercepta precisa ser um dos objetos abaixo:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
Chame vkEnumerateInstanceLayerProperties()
para listar as camadas disponíveis e as propriedades delas. A Vulkan ativa camadas quando o método
vkCreateInstance()
é executado (links em inglês).
O snippet de código abaixo mostra como um app pode usar a API Vulkan para consultar e ativar camadas de forma programática:
// 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,
Saída padrão do logcat
A camada de validação emite mensagens de aviso e erro no logcat rotuladas com uma
tag VALIDATION
. Uma mensagem de camada de validação tem a aparência a seguir, aqui com
a adição de quebras de linha para facilitar a rolagem:
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)
Ativar o callback de depuração
A extensão Debug Utils VK_EXT_debug_utils
permite que o app crie um
mensageiro de depuração que transmite mensagens da camada de validação para um callback
fornecido pelo aplicativo. Seu dispositivo pode não implementar essa extensão, mas ela é implementada nas
camadas de validação mais recentes. Há também uma extensão descontinuada chamada
VK_EXT_debug_report
, que oferece recursos semelhantes se
VK_EXT_debug_utils
não estiver disponível.
Antes de usar a extensão Debug Utils, confira se o dispositivo ou uma camada de validação carregada é compatível. O exemplo a seguir mostra como verificar isso e registrar um callback se a extensão for compatível com o dispositivo ou com a camada de validação.
// 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); }
Depois que o app registrar e ativar o callback, o sistema encaminhará mensagens de depuração para ele.
#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; }
Usar camadas de validação externas
Não é necessário empacotar camadas de validação no APK. Os dispositivos com o Android 9 (API de nível 28) e versões mais recentes podem usar camadas de validação externas ao seu binário e desativá-las dinamicamente. Siga as etapas desta seção para enviar camadas de validação ao seu dispositivo de teste:
Permitir que o app use camadas de validação externas
As políticas e o modelo de segurança do Android são muito diferentes dos usados por outras plataformas. Para carregar camadas de validação externas, uma das condições a seguir precisa ser verdadeira:
O app de destino é depurável. Essa opção resulta em mais informações de depuração, mas pode afetar negativamente o desempenho do app.
O app de destino é executado em um build userbug do sistema operacional que concede acesso raiz.
Apps direcionados apenas ao Android 11 (API de nível 30) ou versões mais recentes: o arquivo de manifesto de destino do Android inclui o seguinte elemento
meta-data
:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Carregar uma camada de validação externa
Dispositivos com o Android 9 (API de nível 28) e versões mais recentes permitem que a Vulkan carregue a camada de validação do armazenamento local do app. A partir do Android 10 (API de nível 29), a Vulkan também pode carregar a camada de validação de um APK separado. Você pode escolher qualquer método que preferir, desde que a versão do Android seja compatível.
Carregar um binário de camada de validação do armazenamento local do dispositivo
A Vulkan busca o binário no diretório de armazenamento de dados temporário do dispositivo. Portanto, primeiro é necessário enviar o binário para esse diretório usando o Android Debug Bridge (adb), desta maneira:
Use o comando
adb push
para carregar o binário de camada no armazenamento de dados do app no dispositivo:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Use os comandos
adb shell
erun-as
para carregar a camada pelo processo do app. O binário tem o mesmo acesso ao dispositivo que o app, sem exigir acesso à raiz.$ 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
Carregar um binário da camada de validação de outro APK
Você pode usar o adb
para instalar um APK que
contém a camada e, em seguida, ativá-la.
adb install --abi abi path_to_apk
Ativar camadas fora do app
Você pode ativar as camadas da Vulkan individualmente para cada app ou de forma global. As configurações por app persistem em reinicializações, enquanto as propriedades globais são apagadas na reinicialização.
Ativar camadas por app
As etapas a seguir descrevem como ativar camadas por app:
Use as configurações do shell do adb para ativar as camadas:
$ adb shell settings put global enable_gpu_debug_layers 1
Especifique o app de destino para ativar as camadas:
$ adb shell settings put global gpu_debug_app <package_name>
Especifique a lista de camadas a serem ativadas (de cima para baixo), separando cada camada com dois-pontos:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Como temos uma única camada de validação do Khronos, o comando provavelmente terá esta aparência:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
Especifique um ou mais pacotes para pesquisar camadas dentro de:
$ adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
Você pode conferir se as configurações estão ativadas usando os comandos a seguir:
$ 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
Como as configurações aplicadas persistem após a reinicialização do dispositivo, é recomendável apagar as configurações depois que as camadas forem carregadas:
$ 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
Ativar camadas globalmente
Você pode ativar uma ou mais camadas globalmente até a próxima reinicialização. Isso fará com que o dispositivo tente carregar as camadas para todos os aplicativos, incluindo executáveis nativos.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>