لایه‌های اعتبارسنجی Vulkan در Android

اکثر میاناهای برنامه‌سازی کاربردی گرافیک صریح بررسی خطا انجام نمی‌دهند زیرا انجام این کار می‌تواند منجر به جریمه عملکرد شود. Vulkan دارای لایه‌های اعتبارسنجی است که بررسی خطا را درطول توسعه ارائه می‌دهند و از جریمه عملکرد در ساخت نسخه پخش برنامه شما جلوگیری می‌کنند. لایه‌های اعتبارسنجی به یک سازوکار لایه‌بندی با هدف کلی متکی هستند که نقاط ورودی API را رهگیری می‌کند.

لایه اعتبارسنجی Khronos تکی

قبلاً Vulkan چندین لایه اعتبارسنجی ارائه می‌کرد که باید به ترتیب خاصی فعال می‌شدند. از نسخه 1.1.106.0 Vulkan SDK، برنامه شما فقط باید یک لایه اعتبارسنجی، VK_LAYER_KHRONOS_validation، را فعال کند تا همه ویژگی‌های لایه‌های اعتبارسنجی قبلی را دریافت کند.

استفاده از لایه‌های اعتبارسنجی بسته‌بندی‌شده در APK

بسته‌بندی لایه‌های اعتبارسنجی در APK شما سازگاری بهینه را تضمین می‌کند. لایه‌های اعتبارسنجی به‌صورت باینری‌های ازپیش‌ساخته‌شده دردسترس هستند یا از کد منبع قابل‌ساخت هستند.

استفاده از باینری‌های ازپیش‌ساخته

جدیدترین دودویی‌های لایه «اعتبارسنجی Vulkan» در Android را از صفحه انتشار GitHub بارگیری کنید.

ساده‌ترین راه برای افزودن لایه‌ها به APK این است که باینری‌های لایه ازپیش‌ساخته را در دایرکتوری src/main/jniLibs/ ماژول خود استخراج کنید، درحالی‌که دایرکتوری‌های ABI (مانند arm64-v8a یا x86-64) دست‌نخورده باقی بمانند، مانند این:

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

ساختن لایه اعتبارسنجی از کد منبع

برای اشکال‌زدایی در کد منبع لایه اعتبارسنجی، جدیدترین منبع را از مخزن GitHub Khronos Group بکشید و دستورالعمل‌های ساخت را در آنجا دنبال کنید.

تأیید کنید که لایه اعتبارسنجی به‌درستی بسته‌بندی شده است

صرف‌نظر از اینکه با لایه‌های ازپیش‌ساخته Khronos یا لایه‌های ساخته‌شده از منبع می‌سازید، فرایند ساخت یک ساختار فایل نهایی در APK شما مانند زیر تولید می‌کند:

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

فرمان زیر نشان می‌دهد که چگونه تأیید کنید که APK شما حاوی لایه اعتبارسنجی موردانتظار است:

$ 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

فعال کردن لایه اعتبارسنجی درطول ایجاد نمونه

Vulkan API به برنامه اجازه می‌دهد لایه‌ها را درطول ایجاد نمونه فعال کند. نقاط ورودی که لایه رهگیری می‌کند باید یکی از اشیاء زیر را به‌عنوان پارامتر اول داشته باشد:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

برای فهرست کردن لایه‌های دردسترس و ویژگی‌های آن‌ها، vkEnumerateInstanceLayerProperties() را فراخوانی کنید. Vulkan لایه‌ها را هنگام اجرای vkCreateInstance() فعال می‌کند.

تکه‌کد زیر نشان می‌دهد که چگونه یک برنامه می‌تواند از Vulkan API برای پرس‌وجو و فعال کردن لایه‌ها به‌صورت برنامه‌نویسی‌شده استفاده کند:

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

خروجی logcat پیش‌فرض

لایه اعتبارسنجی پیام‌های هشدار و خطا را در logcat با برچسب VALIDATION منتشر می‌کند. پیام لایه اعتبارسنجی به‌صورت زیر است (با شکست خط‌های اضافه‌شده در اینجا برای پیمایش آسان‌تر):

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)

فعال کردن فراخوانی اشکال‌زدایی

افزونه Debug Utils VK_EXT_debug_utils به برنامه شما اجازه می‌دهد پیام‌رسان اشکال‌زدایی ایجاد کند که پیام‌های لایه اعتبارسنجی را به فراخوان برگشتی ارائه‌شده توسط برنامه ارسال می‌کند. ممکن است دستگاهتان این افزونه را پیاده‌سازی نکند، اما در جدیدترین لایه‌های اعتبارسنجی پیاده‌سازی شده است. همچنین افزونه منسوخ‌شده‌ای به‌نام VK_EXT_debug_report وجود دارد که اگر VK_EXT_debug_utils دردسترس نباشد، قابلیت‌های مشابهی ارائه می‌دهد.

قبل‌از استفاده از افزونه Debug Utils، باید مطمئن شوید که دستگاهتان یا لایه اعتبارسنجی بارشده از آن پشتیبانی می‌کند. مثال زیر نشان می‌دهد که چگونه بررسی کنید که آیا افزونه ابزارهای اشکال‌زدایی پشتیبانی می‌شود یا نه و اگر افزونه توسط دستگاه یا لایه اعتبارسنجی پشتیبانی می‌شود، یک فراخوانی برگشتی ثبت کنید.

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

پس‌از اینکه برنامه‌تان ثبت شد و فراخوان را فعال کرد، سیستم پیام‌های اشکال‌زدایی را به آن هدایت می‌کند.

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

استفاده از لایه‌های اعتبارسنجی خارجی

لازم نیست لایه‌های اعتبارسنجی را در APK خود بسته‌بندی کنید؛ دستگاه‌هایی که Android نسخه ۹ (سطح میانای برنامه‌سازی کاربردی ۲۸) و بالاتر را اجرا می‌کنند می‌توانند از لایه‌های اعتبارسنجی خارج از باینری شما استفاده کنند و آن‌ها را به‌صورت پویا خاموش و روشن کنند. مراحل این بخش را برای ارسال لایه‌های اعتبارسنجی به دستگاه آزمایشی‌تان دنبال کنید:

فعال کردن برنامه برای استفاده از لایه‌های اعتبارسنجی خارجی

مدل و خط‌مشی‌های امنیتی Android تفاوت قابل‌توجهی با پلاتفرم‌های دیگر دارد. برای بار کردن لایه‌های اعتبارسنجی خارجی، یکی از شرایط زیر باید برقرار باشد:

  • برنامه هدف قابل‌اشکال‌زدایی است. این گزینه منجر به اطلاعات اشکال‌زدایی بیشتر می‌شود، اما ممکن است بر عملکرد برنامه‌تان تأثیر منفی بگذارد.

  • برنامه هدف در ساخت userdebug سیستم‌عامل اجرا می‌شود که دسترسی ریشه را اعطا می‌کند.

  • فقط برنامه‌هایی که Android 11 (سطح میانای برنامه‌سازی کاربردی ۳۰) یا بالاتر را هدف قرار می‌دهند: فایل مانیفست Android هدف شما شامل عنصر meta-data زیر است:

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

بار کردن لایه اعتبارسنجی خارجی

دستگاه‌هایی که Android نسخه ۹ (سطح میانای برنامه‌سازی کاربردی ۲۸) و بالاتر را اجرا می‌کنند به Vulkan اجازه می‌دهند لایه اعتبارسنجی را از فضای ذخیره‌سازی محلی برنامه‌تان بار کند. از Android 10 (سطح میانای برنامه‌سازی کاربردی ۲۹)، Vulkan می‌تواند لایه اعتبارسنجی را از یک APK جداگانه نیز بار کند. می‌توانید هر روشی را که دوست دارید انتخاب کنید به‌شرطی که نسخه Android شما از آن پشتیبانی کند.

بار کردن باینری لایه اعتبارسنجی از فضای ذخیره‌سازی محلی دستگاه

چون Vulkan به‌دنبال باینری در فهرست راهنمای ذخیره‌سازی موقت داده‌های دستگاهتان می‌گردد، ابتدا باید باینری را بااستفاده از Android Debug Bridge (adb) به‌صورت زیر به آن فهرست راهنما ارسال کنید:

  1. از فرمان adb push برای بار کردن باینری لایه در فضای ذخیره‌سازی داده‌های برنامه‌تان در دستگاه استفاده کنید:

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. از فرمان‌های adb shell و run-as برای بار کردن لایه ازطریق فرایند برنامه‌تان استفاده کنید. یعنی، باینری همان دسترسی به دستگاه را دارد که برنامه بدون نیاز به دسترسی ریشه دارد.

    $ 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. لایه را فعال کنید.

بار کردن باینری لایه اعتبارسنجی از APK دیگر

می‌توانید از adb برای نصب APK که حاوی لایه است استفاده کنید و سپس لایه را فعال کنید.

adb install --abi abi path_to_apk

فعال کردن لایه‌ها در خارج از برنامه

می‌توانید لایه‌های Vulkan را برای هر برنامه یا به‌صورت سراسری فعال کنید. تنظیمات هر برنامه در بازراه‌اندازی‌ها حفظ می‌شود، درحالی‌که ویژگی‌های سراسری در بازراه‌اندازی پاک می‌شود.

فعال کردن لایه‌ها به‌صورت برنامه‌به‌برنامه

مراحل زیر نحوه فعال کردن لایه‌ها را به‌صورت برنامه به برنامه شرح می‌دهد:

  1. از تنظیمات پوسته adb برای فعال کردن لایه‌ها استفاده کنید:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. برنامه هدف را برای فعال کردن لایه‌ها در آن مشخص کنید:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. فهرست لایه‌ها را برای فعال کردن (از بالا به پایین) مشخص کنید و هر لایه را با دونقطه از هم جدا کنید:

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

    ازآنجایی‌که یک لایه اعتبارسنجی Khronos داریم، فرمان احتمالاً به‌صورت زیر خواهد بود:

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. یک یا چند بسته را برای جستجوی لایه‌ها در داخل آن مشخص کنید:

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

بااستفاده از فرمان‌های زیر می‌توانید بررسی کنید که آیا تنظیمات فعال شده‌اند یا نه:

$ 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

چون تنظیماتی که اعمال می‌کنید در بازراه‌اندازی‌های دستگاه باقی می‌ماند، شاید بخواهید تنظیمات را پس‌از بار شدن لایه‌ها پاک کنید:

$ 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

فعال کردن لایه‌ها به‌صورت سراسری

می‌توانید یک یا چند لایه را به‌طور سراسری تا بازراه‌اندازی بعدی فعال کنید. این کار تلاش می‌کند لایه‌ها را برای همه برنامه‌ها، ازجمله فایل‌های اجرایی بومی، بار کند.

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