لا تنفّذ معظم واجهات برمجة التطبيقات للرسومات الصريحة عمليات التحقّق من الأخطاء لأنّ ذلك قد يؤدي إلى انخفاض الأداء. تتضمّن Vulkan طبقات التحقّق التي توفّر عمليات التحقّق من الأخطاء أثناء عملية التطوير، ما يتيح تجنُّب التأثير السلبي في الأداء في الإصدار النهائي من تطبيقك. وتعتمد طبقات التحقّق على آلية عامة للتصنيف الطبقي تعترض نقاط دخول واجهة برمجة التطبيقات.
طبقة التحقّق الفردية من Khronos
في السابق، كانت Vulkan توفّر طبقات متعددة للتحقّق من الصحة كان يجب تفعيلها بترتيب معيّن. بدءًا من إصدار حزمة تطوير البرامج (SDK) 1.1.106.0 Vulkan، يجب أن يفعّل تطبيقك طبقة واحدة للتحقّق، 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 واتّبِع تعليمات الإنشاء الواردة فيه.
التأكّد من أنّ طبقة التحقّق من الصحة مجمّعة بشكلٍ صحيح
بغض النظر عمّا إذا كنت تستخدم الطبقات المُنشأة مسبقًا من 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)
تفعيل وظيفة معاودة الاتصال لتصحيح الأخطاء
تتيح إضافة 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 9 (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأحدث استخدام طبقات التحقّق الخارجية للرمز الثنائي وتشغيلها وإيقافها بشكل ديناميكي. اتّبِع الخطوات الواردة في هذا القسم لنقل طبقات التحقّق إلى جهازك الاختباري:
تفعيل تطبيقك لاستخدام طبقات التحقّق الخارجية
يختلف نموذج الأمان والسياسات في Android بشكل كبير عن المنصات الأخرى. لتحميل طبقات التحقّق الخارجية، يجب استيفاء أحد الشروط التالية:
يجب أن يكون التطبيق المستهدَف قابلاً للتصحيح. يؤدي هذا الخيار إلى توفير المزيد من معلومات تصحيح الأخطاء، ولكن قد يؤثر سلبًا في أداء تطبيقك.
يتم تشغيل التطبيق المستهدَف على إصدار userdebug من نظام التشغيل يمنح إذن الوصول إلى الجذر.
التطبيقات التي تستهدف الإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث فقط: يتضمّن ملف بيان Android المستهدف العنصر
meta-data
التالي:<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
تحميل طبقة التحقّق الخارجية
تتيح الأجهزة التي تعمل بالإصدار 9 من نظام التشغيل Android (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث لـ Vulkan تحميل طبقة التحقّق من صحة البيانات من وحدة التخزين المحلية لتطبيقك. بدءًا من نظام التشغيل Android 10 (المستوى 29 من واجهة برمجة التطبيقات)، يمكن أن يحمّل Vulkan أيضًا طبقة التحقّق من حِزمة APK منفصلة. يمكنك اختيار الطريقة التي تفضّلها طالما أنّ إصدار Android يتيحها.
تحميل ملف ثنائي لطبقة التحقّق من صحة البيانات من مساحة التخزين المحلية على جهازك
بما أنّ Vulkan يبحث عن الملف الثنائي في دليل تخزين البيانات المؤقتة على جهازك، عليك أولاً نقل الملف الثنائي إلى هذا الدليل باستخدام Android Debug Bridge (adb)، وذلك باتّباع الخطوات التالية:
استخدِم الأمر
adb push
لتحميل ملف ثنائي الطبقة إلى مساحة تخزين بيانات تطبيقك على الجهاز:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
استخدِم الأمرَين
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
تحميل ملف ثنائي لطبقة التحقّق من صحة البيانات من حزمة APK أخرى
يمكنك استخدام adb
لتثبيت حزمة APK تحتوي على الطبقة، ثم تفعيل الطبقة.
adb install --abi abi path_to_apk
تفعيل الطبقات خارج التطبيق
يمكنك تفعيل طبقات Vulkan على مستوى كل تطبيق أو على مستوى الجهاز. تستمر إعدادات كل تطبيق على حدة بعد إعادة التشغيل، بينما يتم محو الخصائص العامة عند إعادة التشغيل.
تفعيل الطبقات على أساس كل تطبيق
توضِّح الخطوات التالية كيفية تفعيل الطبقات على أساس كل تطبيق على حدة:
استخدِم إعدادات adb shell لتفعيل الطبقات:
$ adb shell settings put global enable_gpu_debug_layers 1
حدِّد التطبيق المستهدف لتفعيل الطبقات عليه:
$ adb shell settings put global gpu_debug_app <package_name>
حدِّد قائمة بالطبقات المطلوب تفعيلها (من الأعلى إلى الأسفل)، مع الفصل بين كل طبقة وأخرى بنقطتين رأسيتين:
$ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
بما أنّ لدينا طبقة تحقّق واحدة من Khronos، من المرجّح أن يبدو الأمر على النحو التالي:
$ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
حدِّد حزمة واحدة أو أكثر للبحث عن طبقات بداخلها:
$ 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>