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 เริ่มต้น
เลเยอร์การตรวจสอบจะส่งข้อความแสดงคำเตือนและข้อผิดพลาดใน 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 ที่ส่งข้อความเลเยอร์การตรวจสอบไปยังการเรียกกลับที่แอปพลิเคชันระบุ
อุปกรณ์ของคุณอาจไม่ได้ใช้ส่วนขยายนี้ แต่มีการใช้ในเลเยอร์การตรวจสอบล่าสุด
นอกจากนี้ ยังมีส่วนขยายที่เลิกใช้งานแล้วชื่อ
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>