Vulkan Validation Layers on Android

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 validation layers. Validation layers intercept or hook Vulkan entry points for various debug and validation purposes.

Each validation layer can contain definitions for one or more of these entry points, and intercepts the entry points for which it contains definitions. When a validation layer does not define an entry point, the system passes the entry point on to the next layer. Ultimately, an entry point not defined in any layer reaches the driver, the base level, unvalidated.

The Android SDK, NDK, and Vulkan samples include Vulkan validation layers for use during development. You can hook these validation layers into the graphics stack, allowing them to report validation issues. This instrumentation allows you to catch and fix misuses during development.

This page explains how to:

  • Integrate NDK's Layer Binaries.
  • Get source code for validation layers.
  • Verifying Layer Build.
  • Enabling Layers in Vulkan Application.

Add Validation Layers to Project

NDK r12 and later include pre-built validation layer binaries. At instance and device creation time, when requested by your application, the Vulkan loader finds them in the APK installed location and loads them.

To use the pre-built validation layer binaries, either modify the gradle build configuration of your project or manually add the binaries into the JNI libraries directory of your project.

Adding validation layers with Gradle

You can add the validation layer your project using either Android Studio's support for CMake and Ndk-build, or using Studio's experimental plugin for Gradle. In general, you should use the CMake and Ndk-build configuration.

To add the libraries using Android Studio's support for CMake/Ndk-build, add the following to your project's gradle configuration:

sourceSets {
  main {
    jniLibs {
      srcDir "${your-ndk-dir}/sources/third_party/vulkan/src/build-android/jniLibs"

To add the libraries using Android Studio's experimental plugin for Gradle, add the following to your project's gradle configuration:

sources {
  main {
    jniLibs {
      source.srcDir "${your-ndk-dir}/sources/third_party/vulkan/src/build-android/jniLibs"

Adding validation layers to JNI libraries

If configuring your project's gradle build file is not working, you can manually add the validation layer binaries to your project's JNI libraries directory by using the following command line options:

$ cd ${your-app-project-root}
$ mkdir -p app/src/main
$ cp -fr ${your-ndk-dir}/sources/third_party/vulkan/src/build-android/jniLibs app/src/main/

Getting Layer 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.

Verifying 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:


The following example shows how to verify that your APK contains the validation layers as expected:

$ jar -xvf project.apk
 inflated: lib/arm64-v8a/
 inflated: lib/arm64-v8a/
 inflated: lib/arm64-v8a/
 inflated: lib/arm64-v8a/
 inflated: lib/arm64-v8a/

Enabling 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 layers are available
// NOTE:  These are not listed in an arbitrary order.  Threading must be
//        first, and unique_objects must be last.  This is the order they
//        will be inserted by the loader.
const char *instance_layers[] = {

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) {

// Pass desired layers into vkCreateInstance
VkInstanceCreateInfo instance_info = {};
instance_info.enabledLayerCount = instance_layer_request_count;
instance_info.ppEnabledLayerNames = instance_layers;

Enabling the Debug Callback

The Debug Report extension VK_EXT_debug_report allows your application to control layer behavior when an event occurs.

Before using this 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 report extension is available
for (uint32_t i = 0; i < inst_ext_count; i++) {
    if (strcmp(inst_exts[i].extensionName,
        enabled_inst_exts[enabled_inst_ext_count++] =

if (enabled_inst_ext_count == 0)

// Pass the instance extensions into vkCreateInstance
VkInstanceCreateInfo instance_info = {};
instance_info.enabledExtensionCount = enabled_inst_ext_count;
instance_info.ppEnabledExtensionNames = enabled_inst_exts;

PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;

vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)
    vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)
    vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");


// Create the debug callback with desired settings
VkDebugReportCallbackEXT debugReportCallback;
if (vkCreateDebugReportCallbackEXT) {
    VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfo;
    debugReportCallbackCreateInfo.sType =
    debugReportCallbackCreateInfo.pNext = NULL;
    debugReportCallbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
                                          VK_DEBUG_REPORT_WARNING_BIT_EXT |
    debugReportCallbackCreateInfo.pfnCallback = DebugReportCallback;
    debugReportCallbackCreateInfo.pUserData = NULL;

    vkCreateDebugReportCallbackEXT(instance, &debugReportCallbackCreateInfo,
                                   nullptr, &debugReportCallback);

// Later, when shutting down Vulkan, call the following
if (vkDestroyDebugReportCallbackEXT) {
   vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, 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>

static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(
                                   VkDebugReportFlagsEXT msgFlags,
                                   VkDebugReportObjectTypeEXT objType,
                                   uint64_t srcObject, size_t location,
                                   int32_t msgCode, const char * pLayerPrefix,
                                   const char * pMsg, void * pUserData )
   if (msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
                           "ERROR: [%s] Code %i : %s",
                           pLayerPrefix, msgCode, pMsg);
   } else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
                           "WARNING: [%s] Code %i : %s",
                           pLayerPrefix, msgCode, pMsg);
                           "PERFORMANCE WARNING: [%s] Code %i : %s",
                           pLayerPrefix, msgCode, pMsg);
   } else if (msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
                           "AppName", "INFO: [%s] Code %i : %s",
                           pLayerPrefix, msgCode, pMsg);
   } else if (msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
                           "AppName", "DEBUG: [%s] Code %i : %s",
                           pLayerPrefix, msgCode, pMsg);

   // 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;