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

اکثر APIهای گرافیکی صریح بررسی خطا را انجام نمی دهند زیرا انجام این کار می تواند منجر به جریمه عملکرد شود. 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 بکشید و دستورالعمل های ساخت را در آنجا دنبال کنید.

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

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

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

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

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

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

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

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

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

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

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

از آنجایی که 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>