רוב ממשקי ה-API הגרפיים הגלויים לא מבצעים בדיקת שגיאות, כי פעולה כזו עלולה לפגוע בביצועים. ל-Vulkan יש שכבות אימות שמספקות בדיקת שגיאות במהלך הפיתוח, וכך נמנעת פגיעה בביצועים בגרסת ההפצה של האפליקציה. שכבות האימות מבוססות על מנגנון שכבות למטרות כלליות שמיירט נקודות כניסה של API.
שכבת אימות יחידה של Khronos
בעבר, Vulkan סיפק כמה שכבות אימות שהיה צריך להפעיל אותן בסדר מסוים. החל מגרסה 1.1.106.0 של Vulkan SDK, באפליקציה שלכם צריך להפעיל רק שכבת אימות אחת, VK_LAYER_KHRONOS_validation
, כדי לקבל את כל התכונות משכבות האימות הקודמות.
שימוש בשכבות אימות שנארזו ב-APK
שכבות אימות של חבילות בתוך ה-APK מבטיחות תאימות אופטימלית. שכבות האימות זמינות כקבצים בינאריים מוכנים מראש או שאפשר ליצור אותן מקוד המקור.
שימוש בקבצים בינאריים מוכנים מראש
מורידים את קובצי ה-binary העדכניים של שכבת האימות של 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 ולפעול לפי הוראות הבנייה שמופיעות שם.
מוודאים ששכבת האימות ארוזה בצורה נכונה
בין אם אתם יוצרים את ה-build באמצעות השכבות המוכנות מראש של Khronos או באמצעות שכבות שנוצרו ממקור, תהליך ה-build יוצר מבנה קבצים סופי ב-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
הפעלת שכבת אימות במהלך יצירת מופע
ה-API של 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)
הפעלת קריאה חוזרת (callback) לניפוי באגים
התוסף 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 (רמת API 28) ומעלה אפשר להשתמש בשכבות אימות חיצוניות לקובץ הבינארי ולהפעיל או להשבית אותן באופן דינמי. כדי להעביר שכבות אימות למכשיר הבדיקה, פועלים לפי השלבים שבקטע הזה:
הפעלת האפשרות להשתמש בשכבות אימות חיצוניות באפליקציה
מודל האבטחה והמדיניות של Android שונים באופן משמעותי מפלטפורמות אחרות. כדי לטעון שכבות אימות חיצוניות, אחד מהתנאים הבאים צריך להתקיים:
אפליקציית היעד היא ניתנת לניפוי באגים. האפשרות הזו מניבה יותר מידע על ניפוי באגים, אבל היא עלולה להשפיע לרעה על ביצועי האפליקציה.
אפליקציית היעד מופעלת בגרסת userdebug של מערכת ההפעלה שמעניקה גישת root.
אפליקציות שמטרגטות ל-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 נפרד. אתם יכולים לבחור את השיטה שנוחה לכם, בתנאי שגרסת Android שלכם תומכת בה.
טעינת קובץ בינארי של שכבת אימות מהאחסון המקומי של המכשיר
מערכת Vulkan מחפשת את הקובץ הבינארי בספרייה של אחסון הנתונים הזמני במכשיר, ולכן צריך קודם להעביר את הקובץ הבינארי לספרייה הזו באמצעות Android Debug Bridge (adb), באופן הבא:
משתמשים בפקודה
adb push
כדי לטעון את הקובץ הבינארי של שכבת המידע לאחסון הנתונים של האפליקציה במכשיר:$ adb push libVkLayer_khronos_validation.so /data/local/tmp
משתמשים בפקודות
adb shell
ו-run-as
כדי לטעון את השכבה דרך תהליך האפליקציה. כלומר, לקובץ הבינארי יש גישה למכשיר כמו לאפליקציה, בלי שהוא צריך גישת root.$ 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>