page.title=Vulkan Validation Layers on Android @jd:body

On this page

  1. Getting Layer Source
  2. Android Studio Integration
    1. Building Layers
    2. Installing Layers
  3. Integrating on the Command Line
    1. Building Layers
    2. Installing Layers
  4. Verifying Layer Build
  5. Enabling Layers
  6. Enabling the Debug Callback

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:

Getting Layer Source

This section explains how to build layers from source. If you have precompiled layers, you can skip this section, and instead read about how to install your layers using Android Studio or from the command line.

From the NDK (Recommended)

NDK Revision 12 and later contains source code for Android validation layers that is known-good, and ready to build. This code resides under the {@code <ndk-root>/sources/third_party/vulkan/src/build-android/generated/gradle-build} directory. This version of the layers should be sufficient for most needs. If so, your next task is to build them. Alternatively, you can pull source code from the Khronos Group repository.

From the repository

Although we recommend that you use the source code provided with the NDK, you can also pull more recent versions of the source code directly from the GitHub repository belonging to the Khronos Group. To do so, perform the following steps.

  1. Clone the Vulkan directory by entering the following command in your terminal window:
    $ git clone git@github.com:KhronosGroup/Vulkan-LoaderAndValidationLayers.git
    

    Note: You must have a private SSH key associated with GitHub, or this command fails with a {@code Permission denied (publickey)} message.

  2. Navigate to the directory containing the layer source code, and check out the repo's stable Android branch, called {@code android_layers}:
    $ cd Vulkan-LoaderAndValidationLayers
    $ git checkout android_layers
    
  3. Begin preparation for building by entering the following commands on the command line:
  4. Continue by following the build instructions for your platform. These instructions are in the {@code BUILD.md} file contained in the local instance of the repository you cloned.

Android Studio Integration

Android Studio builds the validation layers when it builds the rest of the app. This flow makes it easier for you to trace through the layers at runtime. Each layer's source code corresponds to a single Gradle project, which you can specify directly in your Android Studio app. For example, there is a {@code build.gradle} project for threading, and another one for parameter validation.

Building layers

To integrate layers directory into Android Studio application, perform these steps:

  • Add layers to your Android Studio application's project by specifying their corresponding Gradle projects in {@code settings.gradle}, which is normally a peer to app directory. The following example shows how to do this, based on the assumption that you're using the {@code build.gradle} files from the NDK.
    // configure your path to the source code generated on your machine
    def layerProjRoot = file('/path/to/ndk-root/.../build-android/generated/gradle-build')
    String[] layers = ['threading',
                       'parameter_validation',
                       'object_tracker',
                       'core_validation',
                       'device_limits',
                       'image',
                       'swapchain',
                       'unique_objects']
    for (layer in layers) {
        include ":"+ layer
        project(":" + layer.toString()).projectDir = new File("${layerProjRoot}/${layer}")
    }
    
  • Your next step is to provide the built layers to the app by installing them.

    Installing layers

  • To install your layers, add the layer Gradle projects to your application's jniLibs dependencies in your {@code build.gradle} module. This module normally resides under the {@code app/} directory. The following example shows how to do this:
    android.sources {
        main {
            jni { ... }
            jniLibs {
                dependencies {
                    project ":threading"
                    project ":parameter_validation"
                    project ":object_tracker"
                    project ":core_validation"
                    project ":device_limits"
                    project ":image"
                    project ":swapchain"
                    project ":unique_objects"
                }
            }
        }
    } // android.sources
    
  • Develop, build, and debug as you usually would. When you build, Android Studio automatically builds the layers and copies them into your APK.
  • Debug your application. Android Studio allows you to trace through the layer source code.
  • For best performance, remove the layers before you do your release build.
  • From the Command Line

    This section explains how to build and install your layers if your project does not use Android Studio.

    Building layers

    To build validation layers on Linux or OS X, enter these commands on the command line:

    To build validation layers on Windows, enter these commands on the command line:

    Installing layers

    After building the layers, you must provide them to your app. To do so, you must first create a {@code jniLibs} folder in your app's project directory under {@code ./src/main/}, and copy the libs to it. The following example shows how to do this.

    $ mkdir ./src/main/jniLibs
    

    The next step depends on whether you are using Gradle or Android makefiles. If you're using Gradle, each built layer resides in its own directory. Consolidate the layers into a single directory, as the following example shows:

    $ cp -r .../build-android/generated/gradle-build/threading/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/parameter_validation/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/object_tracker/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/core_validation/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/device_limits/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/image/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/swapchain/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    $ cp -r .../build-android/generated/gradle-build/unique_objects/build/outputs/native/debug/all/lib/* ./src/main/jniLibs/
    
    If you're using Android makefiles, the built layers reside in {@code lib} folders, with one {@code lib} folder under each architecture’s root directory. Consolidate the makefiles under the {@code jniLibs} directory as this example shows:

    $ cp -r .../build-android/libs/* ./src/main/jniLibs/
    

    Verifying Layer Build

    Regardless of whether you build using Gradle or Android makefiles, the build process produces a file structure like the following:

    src/main/jniLibs/
      arm64-v8a/
        libVkLayer_core_validation.so
        libVkLayer_device_limits.so
        libVkLayer_image.so
        libVkLayer_object_tracker.so
        libVkLayer_parameter_validation.so
        libVkLayer_swapchain.so
        libVkLayer_threading.so
        libVkLayer_unique_objects.so
      armeabi-v7a/
        libVkLayer_core_validation.so
        ...
    

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

    $ jar -xvf project.apk
     ...
     inflated: lib/arm64-v8a/libVkLayer_threading.so
     inflated: lib/arm64-v8a/libVkLayer_object_tracker.so
     inflated: lib/arm64-v8a/libVkLayer_swapchain.so
     inflated: lib/arm64-v8a/libVkLayer_unique_objects.so
     inflated: lib/arm64-v8a/libVkLayer_parameter_validation.so
     inflated: lib/arm64-v8a/libVkLayer_image.so
     inflated: lib/arm64-v8a/libVkLayer_core_validation.so
     inflated: lib/arm64-v8a/libVkLayer_device_limits.so
     ...
    

    Enabling Layers

    The Vulkan API allows an app to enable both instance layers and device layers.

    Instance layers

    A layer that can intercept Vulkan instance-level entry points is called an instance layer. Instance-level entry points are those with {@code VkInstance} or {@code VkPhysicalDevice} as the first parameter.

    You can call {@code vkEnumerateInstanceLayerProperties()} to list the available instance layers and their properties. The system enables instance layers when {@code vkCreateInstace()} executes.

    The following code snippet shows how an app can use the Vulkan API to programmatically enable and query an instance layer:

    // Get instance layer count using null pointer as last parameter
    uint32_t instance_layer_present_count = 0;
    vkEnumerateInstanceLayerProperties(&instance_layer_present_count, nullptr);
    
    // Enumerate instance 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 instance 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[] = {
        "VK_LAYER_GOOGLE_threading",
        "VK_LAYER_LUNARG_parameter_validation",
        "VK_LAYER_LUNARG_object_tracker",
        "VK_LAYER_LUNARG_core_validation",
        "VK_LAYER_LUNARG_device_limits",
        "VK_LAYER_LUNARG_image",
        "VK_LAYER_LUNARG_swapchain",
        "VK_LAYER_GOOGLE_unique_objects"
    };
    
    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 instance layers 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;
    ...
    

    Device layers

    A layer that can intercept device-level entry points is called a device layer. Device-level entry points are those whose first parameter is {@code VkDevice}, {@code VkCommandBuffer}, or {@code VkQueue}. The list of device layers to enable is included in the {@code ppEnabledLayerNames} field of the {@code VkDeviceCreateInfo} struct that the app passes into {@code vkCreateDevice()}.

    You can call {@code vkEnumerateDeviceLayerProperties} to list the available layers and their properties. The system enables device layers when it calls {@code vkCreateDevice()}.

    The following code snippet shows how an app can use the Vulkan API to programmatically enable a device layer.

    
    // Get device layer count using null as last parameter
    uint32_t device_layer_present_count = 0;
    vkEnumerateDeviceLayerProperties(&device_layer_present_count, nullptr);
    
    // Enumerate device layers with valid pointer in last parameter
    VkLayerProperties* layer_props =
       (VkLayerProperties *)malloc(device_layer_present_count * sizeof(VkLayerProperties));
    vkEnumerateDeviceLayerProperties(physical_device, device_layer_present_count, layer_props));
    
    // Make sure the desired device validation layers are available
    // Ensure threading is first and unique_objects is last!
    const char *device_layers[] = {
        "VK_LAYER_GOOGLE_threading",
        "VK_LAYER_LUNARG_parameter_validation",
        "VK_LAYER_LUNARG_object_tracker",
        "VK_LAYER_LUNARG_core_validation",
        "VK_LAYER_LUNARG_device_limits",
        "VK_LAYER_LUNARG_image",
        "VK_LAYER_LUNARG_swapchain",
        "VK_LAYER_GOOGLE_unique_objects"
    };
    
    uint32_t device_layer_request_count =
       sizeof(device_layers) / sizeof(device_layers[0]);
    for (uint32_t i = 0; i < device_layer_request_count; i++) {
        bool found = false;
        for (uint32_t j = 0; j < device_layer_present_count; j++) {
            if (strcmp(device_layers[i],
               layer_props[j].layerName) == 0) {
                found = true;
            }
        }
        if (!found) {
            error();
        }
    }
    
    // Pass desired device layers into vkCreateDevice
    VkDeviceCreateInfo device_info = {};
    device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    device_info.enabledLayerCount = device_layer_request_count;
    device_info.ppEnabledLayerNames = device_layers;
    ...
    

    Enabling the Debug Callback

    The Debug Report extension {@code 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,
        VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0) {
            enabled_inst_exts[enabled_inst_ext_count++] =
                VK_EXT_DEBUG_REPORT_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_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT;
    PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
    
    vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)
        vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
    vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)
        vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
    
    assert(vkCreateDebugReportCallbackEXT);
    assert(vkDestroyDebugReportCallbackEXT);
    
    // Create the debug callback with desired settings
    VkDebugReportCallbackEXT debugReportCallback;
    if (vkCreateDebugReportCallbackEXT) {
        VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfo;
        debugReportCallbackCreateInfo.sType =
            VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
        debugReportCallbackCreateInfo.pNext = NULL;
        debugReportCallbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
                                              VK_DEBUG_REPORT_WARNING_BIT_EXT |
                                              VK_DEBUG_REPORT_PERFORMANCE_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) {
           __android_log_print(ANDROID_LOG_ERROR,
                               "AppName",
                               "ERROR: [%s] Code %i : %s",
                               pLayerPrefix, msgCode, pMsg);
       } else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
           __android_log_print(ANDROID_LOG_WARN,
                               "AppName",
                               "WARNING: [%s] Code %i : %s",
                               pLayerPrefix, msgCode, pMsg);
       } else if (msgFlags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
           __android_log_print(ANDROID_LOG_WARN,
                               "AppName",
                               "PERFORMANCE WARNING: [%s] Code %i : %s",
                               pLayerPrefix, msgCode, pMsg);
       } else if (msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
           __android_log_print(ANDROID_LOG_INFO,
                               "AppName", "INFO: [%s] Code %i : %s",
                               pLayerPrefix, msgCode, pMsg);
       } else if (msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
           __android_log_print(ANDROID_LOG_VERBOSE,
                               "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;
    }