From baa56d4d8e00e9bda77f5f945b21fb64790f872c Mon Sep 17 00:00:00 2001 From: David Friedman Date: Wed, 10 Feb 2016 20:30:02 -0800 Subject: [PATCH] Docs: Adds validation-layer material to new Vulkan doc set Bug: 27136585 Change-Id: Ie404bb7a334ca4e5e9ea0ff8c156509069333421 --- .../ndk/guides/graphics/validation-layer.jd | 603 ++++++++++++++++++ 1 file changed, 603 insertions(+) create mode 100644 docs/html/ndk/guides/graphics/validation-layer.jd diff --git a/docs/html/ndk/guides/graphics/validation-layer.jd b/docs/html/ndk/guides/graphics/validation-layer.jd new file mode 100644 index 0000000000000..3a61f1847bbb8 --- /dev/null +++ b/docs/html/ndk/guides/graphics/validation-layer.jd @@ -0,0 +1,603 @@ +page.title=Vulkan Validation Layers on Android +@jd:body + +
+ +
+ +

+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. + +
  3. +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
    +
    +
  4. + +
  5. +Prepare to build by following the preparation instructions for your platform. These instructions +are in the {@code BUILD.md} file contained in the local instance of the repository you cloned. +
  6. + +
+ +

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

    +
      +
    1. +Using Gradle: +
      +$ cd build-android
      +$ ./android-generate
      +$ cd generated/gradle-build
      +$ # configure SDK and NDK path in local.properties
      +$ gradlew assembleAllDebug
      +
      +
    2. +
    3. +Using Android makefiles: +
      +$ cd build-android
      +$ ./android-generate
      +$ ndk-build
      +
      +
    4. +
    + +

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

    +
      +
    1. +Using Gradle: +
      +> cd build-android
      +> android-generate.bat
      +> cd generated\gradle-build
      +> REM configure SDK and NDK path in local.properties
      +> gradlew.bat assembleAllDebug
      +
      +
    2. +
    3. +Using Android makefiles: +
      +> cd build-android
      +> android-generate.bat
      +> ndk-build.cmd
      +
      +
    4. +
    + + + +

    + + + +

    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;
    +}
    +
    + + +