Most explicit graphics APIs do not perform error-checking, because doing so can result in a performance penalty. Vulkan provides error-checking in a manner that lets you use this feature at development time, but exclude it from the release build of your app, thus avoiding the penalty when it matters most. You do this by enabling the Vulkan validation layer. This validation layer intercepts or hooks Vulkan entry points for various debug and validation purposes.
The validation layer intercepts the entry points for which it contains definitions. An entry point not defined in the layer reaches the driver, the base level, unvalidated.
The Android NDK and Vulkan samples include the Vulkan validation layer for use during development. You can hook the validation layer into the graphics stack, allowing it to report validation issues. This instrumentation allows you to catch and fix misuses during development.
The single Khronos validation layer
Vulkan layers can be inserted by the loader in a stack so that higher-level
layers will call into the layer below with the layer stack eventually terminating
at the device driver. In the past, there were multiple validation layers that
were enabled in a specific order on Android. Now, however, there is a single
layer, VK_LAYER_KHRONOS_validation
, that encompasses all of the
previous validation layer behavior. For Vulkan validation, all applications
should enable the single validation layer, VK_LAYER_KHRONOS_validation
.
Package the validation layer
The NDK includes a pre-built validation layer binary that you can push to your test device by packaging it in your APK. You can find this binary in the following directory:ndk-dir/sources/third_party/vulkan/src/build-android/jniLibs/abi/
When requested by your app, the Vulkan loader finds and loads the layer from
your app’s APK.
Package the validation layer into your APK with Gradle
You can add the validation layer to your project using the Android Gradle Plugin and Android Studio's support for CMake and ndk-build. To add the libraries using Android Studio's support for CMake and ndk-build, add the following to your app module'sbuild.gradle
file:
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" } } }To learn more about Android Studio’s support for CMake and ndk-build, read Add C and C++ code to your project.
Package the validation layer into JNI libraries
You can manually add the validation layer binary to your project's JNI libraries directory by using the following command line options:$ cd project-root $ mkdir -p app/src/main $ cp -fr ndk-path/sources/third_party/vulkan/src/build-android/jniLibs app/src/main/
Build layer binary from source
If your app needs the latest validation layer, you can pull the latest source from the Khronos Group GitHub repository and follow the build instructions there.
Verify layer build
Regardless of whether you build with NDK's prebuilt layers or you build from the latest source code, the build process produces final file structure like the following:
src/main/jniLibs/ arm64-v8a/ libVkLayer_khronos_validation.so armeabi-v7a/ libVkLayer_khronos_validation.so
The following example shows how to verify that your APK contains the validation layer as expected:
$ jar -xvf project.apk ... inflated: lib/arm64-v8a/libVkLayer_khronos_validation.so ...
Enable layers
The Vulkan API allows an app to enable layers. Layers are enabled during instance creation. Entry points that a layer intercepts must have one of these objects as the first parameter:
VkInstance
VkPhysicalDevice
VkDevice
VkCommandBuffer
VkQueue
You can call vkEnumerateInstanceLayerProperties()
to list the available layers
and their properties. The system enables layers when vkCreateInstance()
executes.
The following code snippet shows how an app can use the Vulkan API to programmatically enable and query a layer:
// 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; ...
Default logcat output
The validation layer emits warning and error messages in logcat labeled with aVALIDATION
tag. A validation layer message looks like the following:
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
Enable the debug callback
The Debug Utils extension VK_EXT_debug_utils
allows your application
to create a debug messenger that will pass validation layer messages to an
application-supplied callback. Note that there is also a deprecated extension,
VK_EXT_debug_report
, that provides similar capability if
VK_EXT_debug_utils
is not available.
Before using the Debug Utils extension, you must first make sure that the platform supports it. The following example shows how to check for debug extension support and register a callback if the extension is supported.
// 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); }
Once your app has registered and enabled the debug callback, the system routes debugging messages to a callback that you register. An example of such a callback appears below:
#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; }
Push layers to your test device with ADB
Follow the steps in this section to push layers to your test device:
Enable debugging
Android's security model and policies differ significantly from other platforms. In order to load external layers, one of the following must be true:
- The target app's manifest file includes the following
meta-data element (only
applies to apps that target Android 11 (API level 30) or higher):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
You should use this option to profile your application. - The target app is debuggable. This option gives you 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 which grants root access.
Load the layers
Devices running Android 9 (API level 28) and higher allow Vulkan to load layers from your app's local storage. Android 10 (API level 29) supports loading layers from a separate APK.
- Layer binaries in your device’s local storage
- An APK that contains the layers, installed separately from the target application
Layer binaries in your device’s local storage
Vulkan looks for the binaries in your device’s temporary data storage directory, so you must first push the binaries to that directory using Android Debug Bridge (ADB), as follows:
-
Use the
adb push
command to load the desired layer binaries into your app’s data storage on the device. The following example pusheslibVkLayer_khronos_validation.so
to the device’s/data/local/tmp
directory:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
-
Use the
adb shell
andrun-as
commands to load the layers through your app process. That is, the binaries have 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
- Enable the layers.
APK that contains the layers
You can use adb
to install the APK
and then enable the layers.
adb install --abi abi path_to_apk
Enable layers outside the application
You can enable layers either per app or globally. Per-app settings persist across reboots, while global properties are cleared on reboot.
To enable layers per app:
# 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>
If want to check whether the settings are enabled, you can do so 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
To disable layers per app:
# 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
To enable layers globally:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>