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

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

طبقة تحقق من Kanronos مفردة

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

استخدام طبقات التحقّق المجمّعة في حزمة APK

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

استخدام برامج ثنائية منشأة مسبقًا

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

إنشاء طبقة التحقق من الصحة من التعليمات البرمجية المصدر

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

التحقق من تجميع طبقة التحقق بشكل صحيح

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

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

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

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

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

  • يتم تشغيل التطبيق المستهدَف على إصدار 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 الجسر (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 لكل تطبيق أو على مستوى العالم. إعدادات مخصّصة حسب التطبيقات persist عبر عمليات إعادة التشغيل، بينما يتم محو الخصائص العامة على إعادة التشغيل.

تمكين الطبقات لكل تطبيق على حدة

توضح الخطوات التالية كيفية تمكين الطبقات على أساس كل تطبيق:

  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>