Android'de Vulkan doğrulama katmanları

En açık grafik API'ler, hata kontrolü yapmaz çünkü performans kaybına neden olabilir. Vulkan'da, geliştirme sırasında hata kontrolü sağlayan doğrulama katmanları bulunur. Böylece, uygulamanızın sürüm derlemesinde performans cezası uygulanmaz. Doğrulama katmanları, API giriş noktalarına müdahale eden genel amaçlı katman oluşturma mekanizmasına dayanır.

Tek Khronos doğrulama katmanı

Daha önce Vulkan, belirli bir sırada etkinleştirilmesi gereken birden fazla doğrulama katmanı sağlıyordu. 1.1.106.0 Vulkan SDK sürümünden itibaren, önceki doğrulama katmanlarındaki tüm özellikleri almak için uygulamanızın sadece tek bir doğrulama katmanını (VK_LAYER_KHRONOS_validation) etkinleştirmesi gerekecek.

APK'nızda paketlenmiş doğrulama katmanlarını kullanma

Doğrulama katmanlarını APK'nızda paketlemek optimum uyumluluğu sağlar. Doğrulama katmanları, önceden oluşturulmuş ikili programlar şeklinde veya kaynak kodundan derlenebilir.

Önceden oluşturulmuş ikili programlar kullanma

GitHub sürüm sayfasından en son Android Vulkan Validation katmanı ikili programlarını indirin.

Katmanları APK'nıza eklemenin en kolay yolu, önceden oluşturulmuş katman ikililerini ABI dizinleri (arm64-v8a veya x86-64 gibi) değişmeden modülünüzün src/main/jniLibs/ dizinine çıkarmaktır:

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

Kaynak kodundan doğrulama katmanı oluşturma

Doğrulama katmanı kaynak kodunda hata ayıklamak için Khronos Group GitHub deposundan en son kaynağı çekip buradaki derleme talimatlarını uygulayın.

Doğrulama katmanının doğru şekilde paketlendiğini doğrulama

Derleme işlemini, Khronos'un önceden oluşturulmuş katmanlarıyla veya kaynaktan derlenmiş katmanlarla yapmanızdan bağımsız olarak, derleme işlemi APK'nızda aşağıdaki gibi son bir dosya yapısı oluşturur:

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

Aşağıdaki komut, APK'nızın beklendiği gibi doğrulama katmanını içerdiğini nasıl doğrulayacağınızı gösterir:

$ 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

Örnek oluşturma sırasında doğrulama katmanı etkinleştirme

Vulkan API, bir uygulamanın örnek oluşturma sırasında katmanları etkinleştirmesine olanak tanır. Bir katmanın kesiştiği giriş noktalarında, ilk parametre olarak aşağıdaki nesnelerden biri olmalıdır:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

Kullanılabilir katmanları ve özelliklerini listelemek için vkEnumerateInstanceLayerProperties() numarasını çağırın. Vulkan, vkCreateInstance() yürütülürken katmanları etkinleştirir.

Aşağıdaki kod snippet'i, uygulamaların, katmanları programatik olarak sorgulamak ve etkinleştirmek için Vulkan API'yi nasıl kullanabileceğini gösterir:

// 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,

Varsayılan logcat çıkışı

Doğrulama katmanı, VALIDATION etiketiyle etiketlenmiş logcat'te uyarı ve hata mesajları yayar. Doğrulama katmanı mesajı aşağıdaki gibi görünür (daha kolay kaydırma için buraya satır sonları eklenmiştir):

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)

Hata ayıklama geri çağırmasını etkinleştir

VK_EXT_debug_utils Debug Utils uzantısı, uygulamanızın doğrulama katmanı mesajlarını uygulama tarafından sağlanan bir geri çağırmaya ileten bir hata ayıklama mesajı oluşturabilmesini sağlar. Cihazınız bu uzantıyı uygulamayabilir, ancak en son doğrulama katmanlarında uygulanmıştır. Ayrıca, VK_EXT_debug_utils kullanılamıyorsa benzer özellikler sunan VK_EXT_debug_report adında kullanımdan kaldırılmış bir uzantı da vardır.

Debug Utils uzantısını kullanmadan önce cihazınızın veya yüklü bir doğrulama katmanının uzantıyı desteklediğinden emin olmalısınız. Aşağıdaki örnekte, hata ayıklama utils uzantısının desteklenip desteklenmediğini nasıl kontrol edeceğiniz ve uzantı, cihaz veya doğrulama katmanı tarafından destekleniyorsa bir geri çağırmanın nasıl kaydedileceği gösterilmektedir.

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

Uygulamanız kaydedilip geri çağırma özelliğini etkinleştirdikten sonra, sistem hata ayıklama mesajlarını uygulamanıza yönlendirir.

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

Harici doğrulama katmanları kullanma

APK'nızda doğrulama katmanlarını paketlemenize gerek yoktur; Android 9 (API düzeyi 28) ve sonraki sürümleri çalıştıran cihazlar, ikili programınızın dışındaki doğrulama katmanları kullanabilir ve bunları dinamik olarak kapatıp açabilir. Doğrulama katmanlarını test cihazınıza aktarmak için bu bölümdeki adımları izleyin:

Uygulamanızın harici doğrulama katmanlarını kullanmasını sağlayın

Android'in güvenlik modeli ve politikaları diğer platformlardan önemli ölçüde farklıdır. Harici doğrulama katmanlarını yüklemek için aşağıdaki koşullardan birinin doğru olması gerekir:

  • Hedef uygulamada hata ayıklama yapılabilir. Bu seçenek, daha fazla hata ayıklama bilgisi sağlar, ancak uygulamanızın performansını olumsuz yönde etkileyebilir.

  • Hedef uygulama, işletim sisteminin kök erişimi veren userdebug derlemesi üzerinde çalıştırılır.

  • Yalnızca Android 11 (API düzeyi 30) veya sonraki sürümleri hedefleyen uygulamalar: Hedef Android manifest dosyanız aşağıdaki meta-data öğesini içeriyor:

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

Harici doğrulama katmanı yükleme

Android 9 (API düzeyi 28) ve sonraki sürümleri çalıştıran cihazlar Vulkan'ın uygulamanızın yerel depolama alanından doğrulama katmanını yüklemesine izin verir. Vulkan, Android 10'dan (API düzeyi 29) itibaren ayrı bir APK'dan doğrulama katmanını da yükleyebilir. Android sürümünüz desteklediği sürece istediğiniz yöntemi seçebilirsiniz.

Cihazınızın yerel depolama alanından bir doğrulama katmanı ikili programı yükleyin

Vulkan, cihazınızın geçici veri depolama dizininde ikili programı aradığı için öncelikle aşağıdaki şekilde Android Hata Ayıklama Köprüsü'nü (adb) kullanarak ikili dosyayı ilgili dizine aktarmanız gerekir:

  1. Katman ikili programını uygulamanızın cihazdaki veri depolama alanına yüklemek için adb push komutunu kullanın:

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. Katmanı uygulama işleminiz aracılığıyla yüklemek için adb shell ve run-as komutlarını kullanın. Diğer bir deyişle, ikili program, uygulamanın root erişimi olmadan sahip olduğu cihaz erişimine sahiptir.

    $ 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. Katmanı etkinleştirin.

Başka bir APK'dan doğrulama katmanı ikili programı yükleme

Katmanı içeren bir APK yüklemek ve ardından katmanı etkinleştirmek için adb kullanabilirsiniz.

adb install --abi abi path_to_apk

Uygulamanın dışındaki katmanları etkinleştir

Vulkan katmanlarını uygulama bazında veya genel olarak etkinleştirebilirsiniz. Uygulama bazında ayarlar yeniden başlatmalar boyunca kalıcı kalır, genel özellikler ise yeniden başlatma sırasında temizlenir.

Katmanları uygulama bazında etkinleştirme

Aşağıdaki adımlarda, katmanların uygulama bazında nasıl etkinleştirileceği açıklanmaktadır:

  1. Katmanları etkinleştirmek için adb kabuk ayarlarını kullanın:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. Katmanların etkinleştirileceği hedef uygulamayı belirtin:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. Etkinleştirilecek katmanların listesini (yukarıdan aşağıya) ve her katmanı iki nokta üst üste ile ayırarak belirtin:

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

    Tek bir Khronos doğrulama katmanımız olduğu için komut aşağıdaki gibi görünür:

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. İçindeki katmanların aranacağı bir veya daha fazla paket belirtin:

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

Aşağıdaki komutları kullanarak ayarların etkinleştirilip etkinleştirilmediğini kontrol edebilirsiniz:

$ 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

Uyguladığınız ayarlar cihaz yeniden başlatmalarında aynı kaldığından, katmanlar yüklendikten sonra ayarları temizlemek isteyebilirsiniz:

$ 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

Katmanları genel olarak etkinleştir

Bir sonraki yeniden başlatmaya kadar bir veya daha fazla katmanı global olarak etkinleştirebilirsiniz. Bu işlem, yerel yürütülebilirler dahil olmak üzere tüm uygulamalar için katmanları yüklemeyi dener.

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