Lapisan validasi Vulkan di Android

Sebagian besar API grafis eksplisit tidak melakukan pemeriksaan error karena dapat menimbulkan masalah performa. Vulkan menyediakan pemeriksaan error dengan cara yang memungkinkan Anda menggunakan fitur ini pada waktu pengembangan, tetapi tidak menyertakannya dalam build rilis aplikasi sehingga akan menghindari masalah semaksimal mungkin. Anda dapat melakukannya dengan mengaktifkan lapisan validasi Vulkan. Lapisan validasi ini mencegat atau mengaitkan titik masuk Vulkan untuk berbagai tujuan debug dan validasi.

Lapisan validasi ini mencegat titik masuk yang berisi definisi. Titik masuk yang tidak ditentukan dalam lapisan akan mencapai driver, yang merupakan tingkat dasar, tanpa divalidasi.

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

Lapisan validasi Khronos tunggal

Lapisan Vulkan dapat disisipkan oleh loader dalam stack sehingga lapisan dengan tingkat yang lebih tinggi akan memanggil lapisan di bawahnya dengan stack lapisan yang pada akhirnya dihentikan di drive perangkat. Sebelumnya, ada beberapa lapisan validasi yang diaktifkan dalam urutan tertentu di Android. Namun, sekarang ada satu lapisan, VK_LAYER_KHRONOS_validation, yang mencakup semua perilaku lapisan validasi sebelumnya. Untuk validasi Vulkan, semua aplikasi harus mengaktifkan lapisan validasi tunggal, VK_LAYER_KHRONOS_validation.

Memaketkan lapisan validasi

NDK menyertakan biner lapisan validasi bawaan yang dapat Anda kirim ke perangkat pengujian dengan mengemasnya dalam APK. Anda dapat menemukan biner ini dalam direktori berikut: ndk-dir/sources/third_party/vulkan/src/build-android/jniLibs/abi/ Saat diminta oleh aplikasi Anda, loader Vulkan akan mencari dan memuat lapisan dari APK aplikasi Anda.

Memaketkan lapisan validasi ke APK menggunakan 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 bagi 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 dukungan Android Studio bagi CMake dan ndk-build lebih lanjut, baca Menambahkan kode C dan C++ ke project Anda.

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/
    

Mem-build biner lapisan dari sumber

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

Memverifikasi build lapisan

Baik Anda mem-build dengan lapisan NDK bawaan maupun dari kode sumber terbaru, proses build akan menghasilkan struktur file akhir seperti berikut:

    src/main/jniLibs/
      arm64-v8a/
        libVkLayer_khronos_validation.so
      armeabi-v7a/
        libVkLayer_khronos_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_khronos_validation.so
     ...
    

Mengaktifkan lapisan

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

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

Anda dapat memanggil vkEnumerateInstanceLayerProperties() untuk mencantumkan lapisan yang tersedia beserta propertinya. Sistem akan mengaktifkan lapisan saat vkCreateInstance() dieksekusi.

Cuplikan kode berikut menunjukkan bagaimana aplikasi dapat menggunakan Vulkan API untuk mengaktifkan dan meminta 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 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;
    ...
    

Mengaktifkan callback debug

Ekstensi Debug Utils VK_EXT_debug_utils memungkinkan aplikasi Anda membuat messenger debug yang akan meneruskan pesan lapisan validasi ke callback yang disediakan aplikasi. Perlu diketahui bahwa ada juga ekstensi yang tidak berlaku lagi, yaitu VK_EXT_debug_report, yang memberikan kemampuan serupa jika VK_EXT_debug_utils tidak tersedia.

Sebelum menggunakan ekstensi Debug Utils, Anda harus memastikan terlebih dahulu bahwa platform mendukungnya. Contoh berikut menunjukkan cara memeriksa 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 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);
    }

    

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>

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

Mengirim lapisan ke perangkat pengujian menggunakan ADB

Ikuti langkah-langkah di bagian ini untuk mengirim lapisan ke perangkat pengujian Anda:

Aktifkan proses debug

Model keamanan dan kebijakan Android sangat berbeda dengan platform lainnya. Untuk memuat lapisan eksternal, salah satu hal berikut harus terpenuhi:

  • File manifes aplikasi target menyertakan elemen meta-data berikut (hanya berlaku untuk aplikasi yang menargetkan Android 11 (API level 'R') atau yang lebih tinggi):
    <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" /> Anda harus menggunakan opsi ini untuk membuat profil aplikasi.
  • Aplikasi target dapat di-debug. Opsi ini memberikan lebih banyak informasi debug, tetapi mungkin akan berdampak buruk pada performa aplikasi Anda.
  • Aplikasi target dijalankan pada build userdebug sistem operasi yang memberikan izin akses root.

Muat lapisan

Perangkat yang menjalankan Android 9 (API level 28) dan yang lebih tinggi memungkinkan Vulkan memuat lapisan dari penyimpanan lokal aplikasi Anda. Android 10 (API level 29) mendukung pemuatan lapisan dari APK yang terpisah.

Biner lapisan di penyimpanan lokal perangkat Anda

Vulkan mencari biner dalam direktori penyimpanan data sementara perangkat Anda, sehingga Anda harus terlebih dahulu mengirim biner ke direktori tersebut menggunakan Android Debug Bridge (ADB) seperti berikut:

  1. Gunakan perintah adb push untuk memuat biner lapisan yang diinginkan ke penyimpanan data aplikasi Anda di perangkat. Contoh berikut mengirim libVkLayer_khronos_validation.so ke direktori /data/local/tmp di perangkat:
        $ adb push libVkLayer_khronos_validation.so /data/local/tmp
        
  2. Gunakan perintah adb shell dan run-as untuk memuat lapisan dalam 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_khronos_validation.so .
        $ adb shell run-as com.example.myapp ls libVkLayer_khronos_validation.so
        
  3. Aktifkan lapisan.

APK yang berisi lapisan

Anda dapat menggunakan adb untuk menginstal APK lalu mengaktifkan lapisan.

    adb install --abi abi path_to_apk
    

Mengaktifkan lapisan di luar aplikasi

Anda dapat mengaktifkan lapisan per aplikasi maupun secara global. Setelan per aplikasi tetap dipertahankan meskipun perangkat di-reboot, sedangkan properti global akan dihapus pada waktu reboot.

Untuk mengaktifkan lapisan per aplikasi:

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

Jika ingin memeriksa apakah setelan 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_KHRONOS_validation
    

Karena setelan yang diterapkan akan dipertahankan meskipun perangkat di-reboot, Anda mungkin perlu 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
    $ adb shell settings delete global gpu_debug_layer_app
    

Untuk menonaktifkan lapisan per aplikasi:

    # 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
    

Untuk mengaktifkan lapisan secara global:

    # This attempts to load layers for all applications, including native
    # executables
    adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>