طبقات التحقق من Vulkan على Android

لا تقوم معظم واجهات برمجة التطبيقات للرسومات الصريحة بالتحقق من الأخطاء لأن ذلك قد يؤدي إلى فرض عقوبة على الأداء. يحتوي Vulkan على طبقات تحقُّق تزوّدنا بفحص الأخطاء أثناء تطوير التطبيق وتجنُّب عقوبات الأداء التي تظهر في إصدار تطبيقك. وتعتمد طبقات التحقُّق على آلية وضع الطبقات للأغراض العامة التي تعترض نقاط دخول واجهة برمجة التطبيقات.

طبقة التحقق من صحة Keronos الفردية

في السابق، كان Vulkan يقدم طبقات تحقق متعددة يجب تفعيلها بترتيب معين. واعتبارًا من إصدار Vulkan SDK 1.1.106.0، يجب على تطبيقك تفعيل طبقة تحقق واحدة، VK_LAYER_KHRONOS_validation، للحصول على جميع الميزات من طبقات التحقق السابقة.

استخدام طبقات التحقق المضمنة في حزمة APK

تضمن طبقات التحقق من صحة الحزمة داخل حزمة APK التوافق الأمثل. تتوفر طبقات التحقق من الصحة كثنائيات سابقة الإنشاء أو يمكن إنشاؤها من رمز المصدر.

استخدام برامج ثنائية سابقة الإنشاء

يمكنك تنزيل أحدث ثنائيات لطبقة Android 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

إنشاء طبقة التحقّق من الصحة من رمز المصدر

لتصحيح الأخطاء في رمز مصدر طبقة التحقق، اسحب أحدث مصدر من مستودع GitHub لمجموعة Khronos Group واتّبِع إرشادات الإنشاء هناك.

التحقق من حزمة التحقق من الصحة بشكل صحيح

بغض النظر عما إذا كنت تقوم بالإنشاء باستخدام الطبقات أو الطبقات المصمَّمة مسبقًا من Keronos والتي تمّ إنشاؤها من المصدر، تنتج عملية التصميم بنية نهائية للملف في حزمة 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 تتيح لتطبيقك إنشاء برنامج تصحيح أخطاء يمرّر رسائل طبقة التحقق من الصحة إلى رد اتصال يوفّره التطبيق. قد لا ينفِّذ جهازك هذه الإضافة، ولكن يتم تنفيذها في أحدث طبقات التحقّق. هناك أيضًا إضافة متوقّفة نهائيًا اسمها VK_EXT_debug_report، وتوفّر هذه الإضافة إمكانات مماثلة في حال عدم توفّر VK_EXT_debug_utils.

قبل استخدام إضافة Debug Utills، يجب التأكّد من أنّ جهازك أو طبقة تحقُّق محمَّلة متوافقة معها. يوضّح المثال التالي كيفية التحقّق مما إذا كانت الإضافة المستخدمة لتصحيح الأخطاء متوافقة وتسجيل معاودة الاتصال إذا كانت الإضافة متوافقة مع الجهاز أو طبقة التحقّق.

// 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، إذ يمكن للأجهزة التي تعمل بالإصدار 9 (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث استخدام طبقات التحقق الخارجية عن برنامجك الثنائي وإيقافها وتشغيلها ديناميكيًا. اتبع الخطوات الواردة في هذا القسم لإرسال طبقات التحقق من الصحة إلى جهاز الاختبار:

تفعيل التطبيق من استخدام طبقات خارجية للتحقق

يختلف نموذج الأمان وسياساته لنظام التشغيل Android بشكل كبير عن الأنظمة الأساسية الأخرى. لتحميل طبقات التحقق الخارجية، يجب أن يكون أحد الشروط التالية صحيحًا:

  • التطبيق المستهدَف قابل للتصحيح ينتج عن هذا الخيار المزيد من معلومات تصحيح الأخطاء، ولكنه قد يؤثر سلبًا على أداء تطبيقك.

  • يتم تشغيل التطبيق المُستهدف على إصدار userdebug من نظام التشغيل الذي يمنح إمكانية الوصول إلى الجذر.

  • التطبيقات التي تستهدف الإصدار Android 11 (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث فقط: يتضمّن ملف بيان Android المستهدف عنصر meta-data التالي:

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

تحميل طبقة تحقُّق خارجية

تسمح الأجهزة التي تعمل بنظام التشغيل Android 9 (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث لتطبيق Vulkan بتحميل طبقة التحقّق من صحة البيانات من مساحة التخزين المحلية لتطبيقك. وبدءًا من نظام التشغيل Android 10 (المستوى 29 من واجهة برمجة التطبيقات)، يمكن لتطبيق Vulkan أيضًا تحميل طبقة التحقق من حزمة APK منفصلة. يمكنك اختيار الطريقة التي تريدها ما دام إصدار Android لديك يدعمها.

تحميل برنامج ثنائي لطبقة التحقّق من مساحة التخزين المحلية في جهازك

نظرًا لأن Vulkan تبحث عن البرنامج الثنائي في دليل تخزين البيانات المؤقتة على جهازك، عليك أولاً إرسال البرنامج الثنائي إلى ذلك الدليل باستخدام Android Debug (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>
    

    نظرًا لأن لدينا طبقة تحقق Keronos واحدة، فمن المحتمل أن يبدو الأمر على النحو التالي:

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