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

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

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

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

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

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

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

آخرین نسخه‌های باینری لایه اعتبارسنجی Vulkan اندروید را از صفحه انتشار 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

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

برای اشکال‌زدایی کد منبع لایه اعتبارسنجی، آخرین منبع را از مخزن گیت‌هاب 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 به یک برنامه اجازه می‌دهد تا لایه‌ها را در حین ایجاد نمونه فعال کند. نقاط ورودی که یک لایه از آنها عبور می‌کند باید یکی از اشیاء زیر را به عنوان اولین پارامتر داشته باشند:

  • VkInstance
  • VkPhysicalDevice
  • VkDevice
  • VkCommandBuffer
  • VkQueue

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

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

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

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

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

قبل از استفاده از افزونه 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);
}

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

#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 خود بسته‌بندی کنید؛ دستگاه‌هایی که اندروید ۹ (سطح API 28) و بالاتر را اجرا می‌کنند می‌توانند از لایه‌های اعتبارسنجی خارج از فایل باینری شما استفاده کنند و آنها را به صورت پویا خاموش و روشن کنند. مراحل این بخش را برای ارسال لایه‌های اعتبارسنجی به دستگاه آزمایشی خود دنبال کنید:

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

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

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

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

  • برنامه‌هایی که فقط اندروید ۱۱ (سطح API 30) یا بالاتر را هدف قرار می‌دهند: فایل مانیفست اندروید هدف شما شامل عنصر meta-data زیر است:

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

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

دستگاه‌هایی که اندروید ۹ (سطح API 28) و بالاتر را اجرا می‌کنند، به Vulkan اجازه می‌دهند تا لایه اعتبارسنجی را از حافظه محلی برنامه شما بارگذاری کند . از اندروید ۱۰ (سطح API 29)، Vulkan همچنین می‌تواند لایه اعتبارسنجی را از یک APK جداگانه بارگذاری کند. تا زمانی که نسخه اندروید شما از آن پشتیبانی کند، می‌توانید هر روشی را که دوست دارید انتخاب کنید.

یک فایل باینری لایه اعتبارسنجی را از حافظه محلی دستگاه خود بارگیری کنید

از آنجا که ولکان به دنبال فایل باینری در دایرکتوری ذخیره‌سازی موقت داده دستگاه شما می‌گردد، ابتدا باید فایل باینری را با استفاده از 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 shell استفاده کنید:

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