Lapisan validasi Vulkan di Android

Sebagian besar API grafis eksplisit tidak melakukan pemeriksaan error karena dapat menyebabkan masalah performa. Vulkan memiliki lapisan validasi yang menyediakan pemeriksaan error selama pengembangan, sehingga menghindari masalah performa dalam build rilis aplikasi Anda. Lapisan validasi mengandalkan mekanisme lapisan tujuan umum yang mencegat titik entri API.

Lapisan validasi Khronos tunggal

Sebelumnya, Vulkan menyediakan beberapa lapisan validasi yang harus diaktifkan dalam urutan tertentu. Mulai dari rilis Vulkan SDK 1.1.106.0, aplikasi Anda hanya perlu mengaktifkan satu lapisan validasi, VK_LAYER_KHRONOS_validation untuk mendapatkan semua fitur dari lapisan validasi sebelumnya.

Menggunakan lapisan validasi yang dipaketkan dalam APK

Memaketkan lapisan validasi dalam APK akan memastikan kompatibilitas yang optimal. Lapisan validasi ini tersedia sebagai biner bawaan atau dapat dibangun dari kode sumber.

Menggunakan biner bawaan

Download biner Lapisan validasi Android Vulkan dari halaman rilis GitHub.

Cara termudah untuk menambahkan lapisan ke APK adalah dengan mengekstrak biner lapisan bawaan ke direktori src/main/jniLibs/ modul Anda, dengan direktori ABI (seperti arm64-v8a atau x86-64) lengkap, seperti ini:

src/main/jniLibs/
  arm64-v8a/
    libVkLayer_khronos_validation.so
  armeabi-v7a/
    libVkLayer_khronos_validation.so
  x86/
    libVkLayer_khronos_validation.so
  x86-64/
    libVkLayer_khronos_validation.so

Mem-build lapisan validasi dari kode sumber

Untuk men-debug kode sumber lapisan validasi, gunakan sumber terbaru dari repositori GitHub Khronos Group dan ikuti petunjuk proses build di sana.

Memverifikasi bahwa lapisan validasi sudah dipaketkan dengan benar

Terlepas dari apakah Anda mem-build dengan lapisan Khronos bawaan atau lapisan yang di-build dari sumber, proses build akan menghasilkan struktur file akhir dalam APK seperti berikut:

lib/
  arm64-v8a/
    libVkLayer_khronos_validation.so
  armeabi-v7a/
    libVkLayer_khronos_validation.so
  x86/
    libVkLayer_khronos_validation.so
  x86-64/
    libVkLayer_khronos_validation.so

Perintah berikut menunjukkan cara memverifikasi bahwa APK Anda berisi lapisan validasi seperti yang diharapkan:

$ jar -tf project.apk | grep libVkLayer
lib/x86_64/libVkLayer_khronos_validation.so
lib/armeabi-v7a/libVkLayer_khronos_validation.so
lib/arm64-v8a/libVkLayer_khronos_validation.so
lib/x86/libVkLayer_khronos_validation.so

Mengaktifkan lapisan validasi selama pembuatan instance

Vulkan API memungkinkan aplikasi mengaktifkan lapisan selama pembuatan instance. Titik entri yang diintersepsi lapisan harus memiliki salah satu objek berikut sebagai parameter pertama:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

Panggil vkEnumerateInstanceLayerProperties() untuk mencantumkan lapisan yang tersedia dan propertinya. Vulkan mengaktifkan lapisan saat vkCreateInstance() dieksekusi.

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

// Enable just the Khronos validation layer.
static const char *layers[] = {"VK_LAYER_KHRONOS_validation"};

// Get the layer count using a null pointer as the last parameter.
uint32_t instance_layer_present_count = 0;
vkEnumerateInstanceLayerProperties(&instance_layer_present_count, nullptr);

// Enumerate layers with a valid pointer in the last parameter.
VkLayerProperties layer_props[instance_layer_present_count];
vkEnumerateInstanceLayerProperties(&instance_layer_present_count, layer_props);

// Make sure selected validation layers are available.
VkLayerProperties *layer_props_end = layer_props + instance_layer_present_count;
for (const char* layer:layers) {
  assert(layer_props_end !=
  std::find_if(layer_props, layer_props_end, [layer](VkLayerProperties layerProperties) {
    return strcmp(layerProperties.layerName, layer) == 0;
  }));
}

// Create a Vulkan instance, requesting all enabled layers or extensions
// available on the system
VkInstanceCreateInfo instanceCreateInfo{
  .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
  .pNext = nullptr,
  .pApplicationInfo = &appInfo,
  .enabledLayerCount = sizeof(layers) / sizeof(layers[0]),
  .ppEnabledLayerNames = layers,

Output logcat default

Lapisan validasi memberikan pesan peringatan dan error di logcat yang diberi label tag VALIDATION. Pesan lapisan validasi terlihat seperti berikut (dengan baris baru ditambahkan di sini agar lebih mudah di-scroll):

Validation -- Validation Error:
  [ VUID-VkDeviceQueueCreateInfo-pQueuePriorities-parameter ]
Object 0: VK_NULL_HANDLE, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0xd6d720c6 |
vkCreateDevice: required parameter
  pCreateInfo->pQueueCreateInfos[0].pQueuePriorities specified as NULL.
The Vulkan spec states: pQueuePriorities must be a valid pointer to an array of
  queueCount float values
  (https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html
  #VUID-VkDeviceQueueCreateInfo-pQueuePriorities-parameter)

Mengaktifkan callback debug

Ekstensi Debug Utils VK_EXT_debug_utils memungkinkan aplikasi Anda membuat pembawa pesan debug yang meneruskan pesan lapisan validasi ke callback yang disediakan aplikasi. Perangkat Anda mungkin tidak menerapkannya, tetapi ekstensi ini diterapkan di lapisan validasi terbaru. Ada juga ekstensi yang disebut VK_EXT_debug_report, yang tidak digunakan lagi dan memberikan kemampuan serupa jika VK_EXT_debug_utils tidak tersedia.

Sebelum menggunakan ekstensi Debug Utils, Anda harus memastikan bahwa perangkat atau lapisan validasi yang dimuat mendukungnya. Contoh berikut menunjukkan cara memeriksa apakah ekstensi debug utils didukung dan mendaftarkan callback jika ekstensi didukung oleh perangkat atau lapisan validasi.

// Get the instance extension count.
uint32_t inst_ext_count = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, nullptr);

// Enumerate the instance extensions.
VkExtensionProperties inst_exts[inst_ext_count];
vkEnumerateInstanceExtensionProperties(nullptr, &inst_ext_count, inst_exts);

// Check for debug utils extension within the system driver or loader.
// Check if the debug utils extension is available (in the driver).
VkExtensionProperties *inst_exts_end = inst_exts + inst_ext_count;
bool debugUtilsExtAvailable = inst_exts_end !=
  std::find_if(inst_exts, inst_exts_end, [](VkExtensionProperties
    extensionProperties) {
    return strcmp(extensionProperties.extensionName,
      VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0;
  });

if ( !debugUtilsExtAvailable ) {
  // Also check the layers for the debug utils extension.
  for (auto layer: layer_props) {
    uint32_t layer_ext_count;
    vkEnumerateInstanceExtensionProperties(layer.layerName, &layer_ext_count,
      nullptr);
    if (layer_ext_count == 0) continue;
    VkExtensionProperties layer_exts[layer_ext_count];
    vkEnumerateInstanceExtensionProperties(layer.layerName, &layer_ext_count,
    layer_exts);

    VkExtensionProperties * layer_exts_end = layer_exts + layer_ext_count;
    debugUtilsExtAvailable = layer_exts != std::find_if(
      layer_exts, layer_exts_end,[](VkExtensionProperties extensionProperties) {
        return strcmp(extensionProperties.extensionName,
        VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0;
      });
    if (debugUtilsExtAvailable) {
        // Add the including layer into the layer request list if necessary.
        break;
    }
  }
}

if (!debugUtilsExtAvailable) return; // since this snippet depends on debugUtils

const char * enabled_inst_exts[] = { ..., VK_EXT_DEBUG_UTILS_EXTENSION_NAME };
uint32_t enabled_extension_count =
  sizeof(enabled_inst_exts)/sizeof(enabled_inst_exts[0]);

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

// NOTE: Can still return VK_ERROR_EXTENSION_NOT_PRESENT if validation layer
// isn't loaded.
vkCreateInstance(&instance_info, nullptr, &instance);

auto pfnCreateDebugUtilsMessengerEXT =
  (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
    tutorialInstance, "vkCreateDebugUtilsMessengerEXT");
auto pfnDestroyDebugUtilsMessengerEXT =
  (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
    tutorialInstance, "vkDestroyDebugUtilsMessengerEXT");

// Create the debug messenger callback with your the settings you want.
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;

  // The DebugUtilsMessenger callback is explained in the following section.
  messengerInfo.pfnUserCallback = &DebugUtilsMessenger;
  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, sistem akan merutekan pesan debug kepadanya.

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

Menggunakan lapisan validasi eksternal

Anda tidak perlu memaketkan lapisan validasi dalam APK; perangkat yang menjalankan Android 9 (API level 28) dan yang lebih tinggi dapat menggunakan lapisan validasi di luar biner Anda serta menonaktifkan dan mengaktifkannya secara dinamis. Ikuti langkah-langkah di bagian ini untuk mengirim lapisan validasi ke perangkat pengujian Anda:

Mengaktifkan aplikasi untuk menggunakan lapisan validasi eksternal

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

  • Aplikasi target dapat di-debug. Opsi ini menghasilkan lebih banyak informasi debug, tetapi dapat berpengaruh negatif terhadap performa aplikasi Anda.

  • Aplikasi target dijalankan pada build userdebug sistem operasi yang memberikan izin akses root.

  • Khusus aplikasi yang menargetkan Android 11 (API level 30) atau yang lebih tinggi: File manifes Android target Anda menyertakan elemen meta-data berikut:

    <meta-data android:name="com.android.graphics.injectLayers.enable"
      android:value="true"/>
    

Memuat lapisan validasi eksternal

Perangkat yang menjalankan Android 9 (API level 28) dan yang lebih tinggi memungkinkan Vulkan memuat lapisan validasi dari penyimpanan lokal aplikasi. Mulai dari Android 10 (API level 29), Vulkan juga dapat memuat lapisan validasi dari APK terpisah. Anda dapat memilih metode mana pun yang disukai selama versi Android Anda mendukungnya.

Memuat biner lapisan validasi dari penyimpanan lokal perangkat

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

  1. Gunakan perintah adb push untuk memuat biner lapisan ke penyimpanan data aplikasi di perangkat:

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. Gunakan perintah adb shell dan run-as untuk memuat lapisan selama proses aplikasi Anda. Dengan kata lain, biner tersebut memiliki akses perangkat yang sama seperti 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.

Memuat biner lapisan validasi dari APK lain

Anda dapat menggunakan adb untuk menginstal APK yang berisi lapisan, lalu mengaktifkan lapisan.

adb install --abi abi path_to_apk

Mengaktifkan lapisan di luar aplikasi

Anda dapat mengaktifkan lapisan Vulkan, baik per aplikasi maupun secara global. Setelan per aplikasi tetap dipertahankan meskipun perangkat dimulai ulang, sedangkan properti global akan dihapus saat perangkat dimulai ulang.

Mengaktifkan lapisan per aplikasi

Langkah-langkah berikut menjelaskan cara mengaktifkan lapisan per aplikasi:

  1. Gunakan setelan shell adb untuk mengaktifkan lapisan:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. Tentukan aplikasi target tempat lapisan akan diaktifkan:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. Tentukan daftar lapisan yang akan diaktifkan (dari atas ke bawah), dengan memisahkan setiap lapisan menggunakan titik dua:

    $ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
    

    Karena kita memiliki lapisan validasi Khronos tunggal, perintahnya mungkin akan terlihat seperti:

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. Tentukan satu atau beberapa paket untuk menelusuri lapisan dalam:

    $ adb shell settings put global
      gpu_debug_layer_app <package1:package2:packageN>
    

Anda dapat memeriksa apakah setelan diaktifkan 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 dimulai ulang, sebaiknya hapus 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

Mengaktifkan lapisan secara global

Anda dapat mengaktifkan satu atau beberapa lapisan secara global hingga perangkat dimulai ulang. Tindakan ini berupaya memuat lapisan untuk semua aplikasi, termasuk file native yang dapat dieksekusi.

$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>