Most explicit graphics APIs don't perform error-checking because doing so can result in a performance penalty. Vulkan has validation layers that provide error-checking during development, avoiding the performance penalty in the release build of your app. Validation layers rely on a general purpose layering mechanism that intercepts API entry points.
Single Khronos validation layer
Previously, Vulkan provided multiple validation layers that needed to be enabled
in a specific order. Beginning with the 1.1.106.0 Vulkan SDK release, your app
only has to enable a single validation
layer,
VK_LAYER_KHRONOS_validation
, to get all features from the previous
validation layers.
Use validation layers packaged in your APK
Packaging validation layers within your APK ensures optimal compatibility. The validation layers are available as prebuilt binaries or are buildable from source code.
Use prebuilt binaries
Download the latest Android Vulkan Validation layer binaries from the GitHub release page.
The easiest way to add the layers to your APK is to extract the prebuilt layer
binaries to the src/main/jniLibs/
directory of your module, with ABI
directories (such as arm64-v8a
or x86-64
) intact, like this:
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
Build the validation layer from source code
To debug into the validation layer source code, pull the latest source from the Khronos Group GitHub repository and follow the build instructions there.
Verify the validation layer is packaged correctly
Regardless of whether you build with the Khronos prebuilt layers or layers built from source, the build process produces a final file structure in your APK like the following:
lib/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so x86/ libVkLayer_khronos_validation.so x86-64/ libVkLayer_khronos_validation.so
The following command shows how to verify that your APK contains the validation layer as expected:
$ 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
Enable a validation layer during instance creation
The Vulkan API allows an app to enable layers during instance creation. Entry points that a layer intercepts must have one of the following objects as the first parameter:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
Call vkEnumerateInstanceLayerProperties()
to list the available layers and their properties. Vulkan enables layers when
vkCreateInstance()
executes.
The following code snippet shows how an app can use the Vulkan API to programmatically query and enable layers:
// 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,
Default logcat output
The validation layer emits warning and error messages in logcat labeled with a
VALIDATION
tag. A validation layer message looks like the following (with
line breaks added here for easier scrolling):
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)
Enable the debug callback
The Debug Utils extension VK_EXT_debug_utils
lets your application create a
debug messenger that passes validation layer messages to an application-supplied
callback. Your device may not implement this extension, but it is implemented in
the most recent validation layers. There's also a deprecated extension called
VK_EXT_debug_report
, which provides similar capabilities if
VK_EXT_debug_utils
is not available.
Before using the Debug Utils extension, you should make sure that your device or a loaded validation layer supports it. The following example shows how to check whether the debug utils extension is supported and register a callback if the extension is supported by either the device or validation layer.
// 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); }
After your app registers and enables the callback, the system routes debugging messages to it.
#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; }
Use external validation layers
You don't have to package validation layers in your APK; devices running Android 9 (API level 28) and higher can use validation layers external to your binary and turn them off and on dynamically. Follow the steps in this section to push validation layers to your test device:
Enable your app to use external validation layers
Android's security model and policies differ significantly from other platforms. To load external validation layers, one of the following conditions must be true:
The target app is debuggable. This option results in more debug information, but might negatively affect the performance of your app.
The target app is run on a userdebug build of the operating system that grants root access.
Apps targeting Android 11 (API level 30) or higher only: Your target Android manifest file includes the following
meta-data
element:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
Load an external validation layer
Devices running Android 9 (API level 28) and higher allow Vulkan to load the validation layer from your app's local storage. Starting in Android 10 (API level 29), Vulkan can also load the validation layer from a separate APK. You can choose whichever method you like as long as your Android version supports it.
Load a validation layer binary from your device’s local storage
Because Vulkan looks for the binary in your device’s temporary data storage directory, you must first push the binary to that directory using Android Debug Bridge (adb), as follows:
Use the
adb push
command to load the layer binary into your app’s data storage on the device:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
Use the
adb shell
andrun-as
commands to load the layer through your app process. That is, the binary has the same device access that the app has without requiring root access.$ 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
Load a validation layer binary from another APK
You can use adb
to install an APK that
contains the layer and then enable the layer.
adb install --abi abi path_to_apk
Enable layers outside the application
You can enable Vulkan layers either per-app or globally. Per-app settings persist across reboots, while global properties are cleared on reboot.
Enable layers on a per-app basis
The following steps describe how to enable layers on a per-app basis:
Use adb shell settings to enable the layers:
$ adb shell settings put global enable_gpu_debug_layers 1
Specify the target application to enable the layers on:
$ adb shell settings put global gpu_debug_app <package_name>
Specify the list of layers to enable (from top to bottom), separating each layer by a colon:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
Since we have a single Khronos validation layer, the command will likely look like:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
Specify one or more packages to search for layers inside of:
$ adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
You can check whether the settings are enabled using the following commands:
$ 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
Because the settings you apply persist across device reboots, you may want to clear the settings after the layers are loaded:
$ 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
Enable layers globally
You can enable one or more layers globally until the next reboot. This attempts to load the layers for all applications, including native executables.
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>