Lapisan validasi Vulkan di Android

Sebagian besar API grafis eksplisit tidak melakukan pengecekan error, karena tindakan tersebut dapat mengakibatkan penalti performa. Vulkan menyediakan pengecekan error dengan cara yang memungkinkan Anda menggunakan fitur ini pada saat pengembangan, tetapi tidak menyertakannya dalam build rilis aplikasi, sehingga menghindari penalti saat pengecekan error benar-benar diperlukan. Untuk tujuan ini, Anda perlu mengaktifkan lapisan validasi. Lapisan validasi mencegat atau mengaitkan titik masuk Vulkan untuk berbagai keperluan debug dan validasi.

Setiap lapisan validasi dapat berisi definisi untuk satu atau beberapa titik masuk tersebut, dan mencegat titik masuk yang berisi definisi. Jika lapisan validasi tidak menetapkan titik masuk, sistem akan meneruskan titik masuk ke lapisan berikutnya. Pada akhirnya, titik masuk yang tidak ditetapkan di lapisan mana pun akan mencapai driver, yang merupakan level terbawah, dalam keadaan tidak tervalidasi.

Sampel Android SDK, NDK, dan Vulkan menyertakan lapisan validasi Vulkan untuk digunakan selama pengembangan. Anda dapat mengaitkan lapisan validasi ini ke tumpukan grafis, untuk memungkinkannya melaporkan masalah validasi. Dengan instrumen ini, Anda dapat merekam dan memperbaiki penyalahgunaan selama pengembangan.

Halaman ini menjelaskan cara:

  • Memuat lapisan validasi ke perangkat pengujian
  • Mendapatkan kode sumber untuk lapisan validasi
  • Memverifikasi build lapisan
  • Mengaktifkan lapisan pada aplikasi Vulkan

Memuat lapisan validasi ke perangkat pengujian

NDK menyertakan biner lapisan validasi yang telah dibuat sebelumnya, yang bisa Anda kirim ke perangkat pengujian dengan mengemasnya ke dalam APK atau memuatnya menggunakan Android Debug Bridge (ADB). Anda dapat menemukan biner ini di direktori berikut: ndk-dir/sources/third_party/vulkan/src/build-android/jniLibs/abi/

Saat diminta oleh aplikasi, pemuat Vulkan akan mencari dan memuat lapisan dari APK aplikasi Anda atau direktori data lokal. Bagian ini menjelaskan beberapa cara mengirim biner lapisan ke perangkat pengujian. Meskipun pemuat Vulkan dapat menemukan biner lapisan dari beberapa sumber pada perangkat, Anda hanya perlu menggunakan salah satu metode yang dijelaskan di bawah.

Mengemas lapisan validasi ke dalam APK dengan Gradle

Anda dapat menambahkan lapisan validasi ke project menggunakan Plugin Android Gradle dan dukungan Android Studio untuk CMake dan ndk-build.

Untuk menambahkan library menggunakan dukungan Android Studio untuk CMake dan ndk-build, tambahkan kode berikut ke file build.gradle modul aplikasi Anda:

    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"
        }
      }
    }
    

Untuk mempelajari lebih lanjut dukungan Android Studio untuk CMake dan ndk-build, baca Menambahkan kode C dan C++ ke project.

Mengemas lapisan validasi ke dalam library JNI

Anda dapat menambahkan biner lapisan validasi secara manual ke direktori library JNI project menggunakan opsi command line berikut:

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

Mengirim biner lapisan ke perangkat pengujian menggunakan ADB

Perangkat yang menjalankan Android 9 (API level 28) dan yang lebih tinggi memungkinkan Vulkan memuat biner lapisan dari penyimpanan lokal perangkat. Artinya, saat memuat biner dari perangkat, Anda tidak perlu lagi memaketkannya dengan APK aplikasi. Namun, aplikasi yang diinstal harus dapat di-debug. Vulkan mencari biner di direktori penyimpanan data sementara perangkat Anda, sehingga Anda harus mengirim biner ke direktori tersebut terlebih dahulu menggunakan Android Debug Bridge (ADB), seperti berikut:

  1. Gunakan perintah adb push untuk memuat biner lapisan yang diinginkan ke dalam penyimpanan data aplikasi Anda di perangkat. Contoh berikut ini mengirim libVkLayer_unique_objects.so ke direktori perangkat /data/local/tmp:
        $ adb push libVkLayer_unique_objects.so /data/local/tmp
        
  2. Gunakan perintah adb shell dan run-as untuk memuat lapisan melalui proses aplikasi Anda. Dengan kata lain, biner tersebut memiliki akses perangkat yang sama dengan yang dimiliki aplikasi tanpa memerlukan akses root.
        $ adb shell run-as com.example.myapp cp /data/local/tmp/libVkLayer_unique_objects.so
        $ adb shell run-as com.example.myapp ls libVkLayer_unique_objects.so
        
  3. Gunakan perintah adb shell settings untuk mengaktifkan Vulkan agar memuat lapisan dari penyimpanan perangkat:
        $ adb shell settings put global enable_gpu_debug_layers 1
        $ adb shell settings put global gpu_debug_app com.example.myapp
        $ adb shell settings put global gpu_debug_layers VK_LAYER_GOOGLE_unique_objects
        

    Tips: Anda juga dapat mengaktifkan setelan ini melalui opsi developer di perangkat. Setelah mengaktifkan opsi developer, buka aplikasi Setelan pada perangkat pengujian, pilih Opsi developer > Proses debug, lalu pastikan opsi untuk Mengaktifkan lapisan debug GPU diaktifkan.

  4. Jika ingin memeriksa apakah setelan dari langkah 3 sudah diaktifkan, Anda dapat melakukannya menggunakan perintah berikut:
        $ adb shell settings list global | grep gpu
        enable_gpu_debug_layers=1
        gpu_debug_app=com.example.myapp
        gpu_debug_layers=VK_LAYER_GOOGLE_unique_objects
        
  5. Karena setelan yang Anda terapkan di langkah 3 dipertahankan meskipun perangkat di-reboot, Anda dapat menghapus setelan setelah lapisan dimuat:
        $ 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
        

Membuat biner lapisan dari sumber

Jika aplikasi Anda memerlukan lapisan validasi terbaru, Anda dapat mengambil sumber terbaru dari repositori GitHub Khronos Group dan mengikuti petunjuk build di sana.

Memverifikasi build lapisan

Entah Anda membuat build dengan lapisan NDK yang telah dibuat sebelumnya atau dari kode sumber terbaru, proses pembuatan build menghasilkan struktur file akhir seperti berikut:

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

Contoh berikut menunjukkan cara memverifikasi bahwa APK Anda memuat lapisan validasi seperti yang diharapkan:

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

Mengaktifkan lapisan

Vulkan API memungkinkan aplikasi mengaktifkan lapisan. Lapisan diaktifkan selama pembuatan instance. Titik masuk yang dicegat lapisan harus memiliki salah satu dari objek berikut sebagai parameter pertamanya:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

Anda dapat memanggil vkEnumerateInstanceLayerProperties() untuk membuat daftar lapisan yang tersedia dan propertinya. Sistem mengaktifkan lapisan saat vkCreateInstance() dijalankan.

Cuplikan kode berikut menunjukkan cara aplikasi dapat menggunakan Vulkan API untuk mengaktifkan dan mengkueri lapisan secara terprogram:

    // 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[] = {
        "VK_LAYER_GOOGLE_threading",
        "VK_LAYER_LUNARG_parameter_validation",
        "VK_LAYER_LUNARG_object_tracker",
        "VK_LAYER_LUNARG_core_validation",
        "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 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;
    ...
    

Mengaktifkan callback debug

Ekstensi Laporan Debug VK_EXT_debug_report memungkinkan aplikasi untuk mengontrol perilaku lapisan saat terjadi suatu peristiwa.

Sebelum menggunakan ekstensi ini, Anda harus terlebih dahulu memastikan bahwa platform mendukungnya. Contoh berikut menunjukkan cara memeriksa ketersediaan dukungan ekstensi debug dan mendaftarkan callback jika ekstensi didukung.

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

    

Setelah aplikasi Anda mendaftarkan dan mengaktifkan callback debug, sistem akan merutekan pesan debug ke callback yang Anda daftarkan. Contoh callback tersebut adalah seperti berikut:

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