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 อนุญาตให้แอปเปิดใช้เลเยอร์ระหว่างการสร้างอินสแตนซ์ จุดเข้าใช้งานที่เลเยอร์ขัดจังหวะต้องมีออบเจ็กต์ต่อไปนี้เป็นพารามิเตอร์แรก
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 เริ่มต้น
เลเยอร์การตรวจสอบจะแสดงคําเตือนและข้อความแสดงข้อผิดพลาดในบันทึกที่ทําเครื่องหมายด้วยแท็ก 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 (API ระดับ 28) ขึ้นไปสามารถใช้เลเยอร์การตรวจสอบภายนอกไบนารีของคุณได้ รวมถึงปิดและเปิดเลเยอร์การตรวจสอบแบบไดนามิก ทําตามขั้นตอนในส่วนนี้เพื่อพุชเลเยอร์การตรวจสอบไปยังอุปกรณ์ทดสอบ
เปิดใช้แอปให้ใช้เลเยอร์การตรวจสอบภายนอก
รูปแบบและนโยบายด้านความปลอดภัยของ Android แตกต่างจากแพลตฟอร์มอื่นๆ อย่างมาก หากต้องการโหลดเลเยอร์การตรวจสอบภายนอก เงื่อนไขข้อใดข้อหนึ่งต่อไปนี้ต้องเป็นจริง
แอปเป้าหมายแก้ไขข้อบกพร่องได้ ตัวเลือกนี้จะแสดงข้อมูลการแก้ไขข้อบกพร่องมากขึ้น แต่อาจส่งผลเสียต่อประสิทธิภาพของแอป
แอปเป้าหมายจะทำงานบนบิลด์ userdebug ของระบบปฏิบัติการที่ให้สิทธิ์เข้าถึงระดับรูท
แอปที่กําหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไปเท่านั้น: ไฟล์ Android Manifest เป้าหมายมีองค์ประกอบ
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
เพื่อโหลดเลเยอร์ผ่านกระบวนการของแอป กล่าวคือ ไบนารีมีสิทธิ์เข้าถึงอุปกรณ์เหมือนกับที่แอปมีโดยไม่ต้องมีสิทธิ์เข้าถึงระดับรูท$ 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
ระบุแพ็กเกจอย่างน้อย 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>