API กราฟิกแบบชัดเจนส่วนใหญ่จะไม่ทำการตรวจสอบข้อผิดพลาด เนื่องจากอาจส่งผลให้ประสิทธิภาพการทำงานลดลง Vulkan มี เลเยอร์การตรวจสอบที่ทำการตรวจสอบข้อผิดพลาดระหว่างการพัฒนา ซึ่งช่วยหลีกเลี่ยงการลดลงของประสิทธิภาพการทำงานในบิลด์ที่เผยแพร่ของแอป เลเยอร์การตรวจสอบใช้กลไกการวางเลเยอร์แบบอเนกประสงค์ที่ดักจับจุดเริ่มต้นของ API
ชั้นการตรวจสอบ Khronos เดียว
ก่อนหน้านี้ Vulkan มีเลเยอร์การตรวจสอบหลายเลเยอร์ที่ต้องเปิดใช้ตามลำดับที่เฉพาะเจาะจง ตั้งแต่ Vulkan SDK เวอร์ชัน 1.1.106.0 เป็นต้นไป แอปของคุณ
จะต้องเปิดใช้เลเยอร์การตรวจสอบ
เพียงเลเยอร์เดียว
VK_LAYER_KHRONOS_validationเพื่อรับฟีเจอร์ทั้งหมดจากเลเยอร์การตรวจสอบ
ก่อนหน้านี้
ใช้เลเยอร์การตรวจสอบที่แพ็กเกจไว้ใน APK
การแพ็กเกจเลเยอร์การตรวจสอบไว้ใน APK จะช่วยให้มั่นใจได้ถึงความเข้ากันได้ที่เหมาะสมที่สุด เลเยอร์การตรวจสอบมีให้ใช้งานเป็นไบนารีที่สร้างไว้ล่วงหน้าหรือสร้างได้จากซอร์สโค้ด
ใช้ไบนารีที่สร้างไว้ล่วงหน้า
ดาวน์โหลดไบนารีชั้นการตรวจสอบ 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
สร้างชั้นการตรวจสอบจากซอร์สโค้ด
หากต้องการแก้ไขข้อบกพร่องในซอร์สโค้ดชั้นการตรวจสอบ ให้ดึงซอร์สโค้ดล่าสุดจาก ที่เก็บ GitHub ของ Khronos Group แล้วทำตาม วิธีการสร้างที่ระบุไว้
ยืนยันว่าแพ็กเกจเลเยอร์การตรวจสอบถูกต้อง
ไม่ว่าคุณจะสร้างด้วยเลเยอร์ที่สร้างไว้ล่วงหน้าของ 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 API อนุญาตให้แอปเปิดใช้เลเยอร์ระหว่างการสร้างอินสแตนซ์ จุดเริ่มต้นที่เลเยอร์ดักจับต้องมีออบเจ็กต์ใดออบเจ็กต์หนึ่งต่อไปนี้เป็นพารามิเตอร์แรก
VkInstanceVkPhysicalDeviceVkDeviceVkCommandBufferVkQueue
เรียกใช้ 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 Utils VK_EXT_debug_utils ช่วยให้แอปพลิเคชันสร้าง Debug Messenger ที่ส่งข้อความชั้นการตรวจสอบไปยัง Callback ที่แอปพลิเคชันจัดหาให้ อุปกรณ์ของคุณอาจไม่ได้ใช้ส่วนขยายนี้ แต่ส่วนขยายนี้มีการใช้งานในเลเยอร์การตรวจสอบล่าสุด นอกจากนี้ยังมีส่วนขยายที่เลิกใช้งานแล้วชื่อ VK_EXT_debug_report ซึ่งมีความสามารถคล้ายกันหาก VK_EXT_debug_utils ไม่พร้อมใช้งาน
ก่อนใช้ส่วนขยาย Debug Utils คุณควรตรวจสอบว่าอุปกรณ์หรือเลเยอร์การตรวจสอบที่โหลดรองรับส่วนขยายดังกล่าว ตัวอย่างต่อไปนี้แสดงวิธีตรวจสอบว่าส่วนขยาย Debug Utils ได้รับการรองรับหรือไม่ และลงทะเบียน Callback หากอุปกรณ์หรือชั้นการตรวจสอบรองรับส่วนขยาย
// 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 ของระบบปฏิบัติการที่ให้สิทธิ์เข้าถึงระดับราก
-
<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เพื่อโหลดเลเยอร์ผ่านกระบวนการของแอป นั่นคือ ไบนารีมีสิทธิ์เข้าถึงอุปกรณ์เหมือนกับที่แอปมีโดยไม่ต้องใช้สิทธิ์เข้าถึงระดับราก$ 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 settings เพื่อเปิดใช้เลเยอร์
$ 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ระบุแพ็กเกจอย่างน้อย 1 รายการที่จะค้นหาเลเยอร์ภายใน
$ 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
เปิดใช้เลเยอร์ทั่วโลก
คุณสามารถเปิดใช้เลเยอร์อย่างน้อย 1 เลเยอร์ทั่วโลกจนกว่าจะรีบูตครั้งถัดไป การดำเนินการนี้จะพยายามโหลดเลเยอร์สำหรับแอปพลิเคชันทั้งหมด รวมถึงไฟล์ปฏิบัติการดั้งเดิม
$ adb shell setprop debug.vulkan.layers <layer1:layer2:layerN>