רוב ממשקי ה-API של הגרפיקה הבוטה לא מבצעים בדיקת שגיאות, מפני שהפעולה הזו עלולה עלולות להוביל לפגיעה בביצועים. ב-Vulkan יש שכבות אימות שמספקות לבדיקת שגיאות במהלך הפיתוח, כדי להימנע מפגיעה בביצועים של גרסת ה-build של האפליקציה שלכם. שכבות אימות מסתמכות על מטרה כללית במנגנון שכבות שמיירט נקודות כניסה ל-API.
שכבת אימות יחידה של כרונו
בעבר, Vulkan סיפק כמה שכבות אימות שצריך להפעיל
בסדר מסוים. החל מגרסה 1.1.106.0 Vulkan SDK, האפליקציה שלך
צריך להפעיל רק אימות אחד
בשכבת זרימת הנתונים,
VK_LAYER_KHRONOS_validation
, כדי לקבל את כל התכונות מהגרסה הקודמת
ארבע שכבות אימות.
שימוש בשכבות אימות שנארזות ב-APK
אריזת שכבות אימות בתוך ה-APK מבטיחה תאימות אופטימלית. שכבות האימות זמינות כקבצים בינאריים מוכנים מראש או שניתן ליצור אותן מ- קוד המקור.
שימוש בקבצים בינאריים מוכנים מראש
מורידים את הקבצים הבינאריים העדכניים של השכבה של Android Vulkan Validation מ-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 GitHub מאגר ולעקוב אחרי את ההוראות ל-build.
מוודאים ששכבת האימות ארוזה כראוי
ולא משנה אם אתם בונים באמצעות השכבות המוכנות מראש של חררון או השכבות שנוצרו מהמקור, תהליך ה-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
הפעלה של שכבת אימות במהלך יצירת מכונה
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)
הפעלת הקריאה החוזרת (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 שונים באופן משמעותי מאלה של אחרים פלטפורמות שונות. כדי לטעון שכבות אימות חיצוניות, צריך אחד מהתנאים הבאים חייב להיות True:
אפליקציית היעד ניתנת לניפוי באגים. אם בוחרים באפשרות הזו, יש יותר ניפוי באגים אבל עשוי להשפיע לרעה על ביצועי האפליקציה שלכם.
אפליקציית היעד רצה ב-build של 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 כדי להפעיל את השכבות:
$ 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>
מכיוון שיש לנו שכבת אימות אחת של חררון, סביר להניח שהפקודה נראים כך:
$ 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>