เลเยอร์การตรวจสอบ Vulkan ใน Android

API กราฟิกที่โจ่งแจ้งส่วนใหญ่จะไม่ทำการตรวจสอบข้อผิดพลาดเนื่องจาก ส่งผลให้มีบทลงโทษด้านประสิทธิภาพ Vulkan มีเลเยอร์การตรวจสอบที่ให้ ในระหว่างการพัฒนา หลีกเลี่ยงบทลงโทษด้านประสิทธิภาพใน เปิดตัวบิลด์ของแอปคุณ เลเยอร์การตรวจสอบความถูกต้องจะขึ้นอยู่กับจุดประสงค์ทั่วไป เลเยอร์กลไกที่ดักจับจุดเข้าถึง API

ชั้นการตรวจสอบ Khronos เดียว

ก่อนหน้านี้ Vulkan มีเลเยอร์การตรวจสอบความถูกต้องหลายชั้นที่จำเป็นต้องเปิดใช้ ตามลำดับที่กำหนด เริ่มตั้งแต่รุ่น 1.1.106.0 Vulkan SDK แอปของคุณ ต้องเปิดใช้การตรวจสอบความถูกต้องเพียงครั้งเดียว เลเยอร์ VK_LAYER_KHRONOS_validation หากต้องการใช้ฟีเจอร์ทั้งหมดจาก เลเยอร์การตรวจสอบความถูกต้อง

ใช้เลเยอร์การตรวจสอบความถูกต้องแพ็กเกจใน APK

เลเยอร์การตรวจสอบแพ็กเกจภายใน APK ช่วยให้มั่นใจได้ว่ามีความเข้ากันได้อย่างเหมาะสม เลเยอร์การตรวจสอบความถูกต้องพร้อมใช้งานเป็นไบนารีที่สร้างไว้ล่วงหน้าหรือสร้างได้จาก ซอร์สโค้ด

ใช้ไบนารีที่สร้างไว้ล่วงหน้า

ดาวน์โหลดไบนารีของเลเยอร์การตรวจสอบความถูกต้องของ 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

สร้างเลเยอร์การตรวจสอบจากซอร์สโค้ด

ในการแก้ไขข้อบกพร่องในซอร์สโค้ดของเลเยอร์การตรวจสอบ ให้ดึงแหล่งที่มาล่าสุดจาก Khronos Group GitHub ที่เก็บ และทำตาม วิธีการสร้างที่นั่น

ตรวจสอบว่าได้จัดแพ็กเกจเลเยอร์การตรวจสอบอย่างถูกต้อง

ไม่ว่าคุณจะสร้างด้วยเลเยอร์ที่สร้างไว้ล่วงหน้าของ 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)

เปิดใช้ Callback ของการแก้ไขข้อบกพร่อง

ส่วนขยาย Debug Utils VK_EXT_debug_utils ช่วยให้แอปพลิเคชันสร้าง โปรแกรมรับส่งข้อความแก้ไขข้อบกพร่องที่ส่งข้อความเลเยอร์การตรวจสอบความถูกต้องไปยังแอปพลิเคชัน Callback อุปกรณ์อาจไม่ติดตั้งส่วนขยายนี้ แต่มีการติดตั้งใช้งานใน เลเยอร์การตรวจสอบความถูกต้องล่าสุด นอกจากนี้ยังมีส่วนขยายที่เลิกใช้งานแล้วชื่อ VK_EXT_debug_report ซึ่งมีความสามารถที่คล้ายกัน VK_EXT_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);
}

หลังจากที่แอปลงทะเบียนและเปิดใช้ Callback แล้ว ระบบจะกำหนดเส้นทางการแก้ไขข้อบกพร่อง ข้อความไปที่ข้อความนี้

#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 ก่อน ตัวเชื่อม (adb) ดังนี้

  1. ใช้คำสั่ง adb push เพื่อโหลด ไบนารีของเลเยอร์ลงในพื้นที่เก็บข้อมูลของแอปในอุปกรณ์ ดังนี้

    $ adb push libVkLayer_khronos_validation.so /data/local/tmp
    
  2. ใช้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
    
  3. เปิดใช้งานเลเยอร์

โหลดไบนารีของเลเยอร์การตรวจสอบจาก APK อื่น

คุณสามารถใช้ adb เพื่อติดตั้ง APK ที่ มีเลเยอร์นั้นอยู่ แล้วเปิดใช้เลเยอร์

adb install --abi abi path_to_apk

เปิดใช้เลเยอร์นอกแอปพลิเคชัน

คุณสามารถเปิดใช้เลเยอร์ Vulkan ต่อแอปหรือทั่วโลกได้ การตั้งค่าของแต่ละแอป คงอยู่ในการรีบูต ขณะที่พร็อพเพอร์ตี้ส่วนกลางมีการล้าง รีบูต

เปิดใช้เลเยอร์ในแอปแต่ละรายการ

ขั้นตอนต่อไปนี้อธิบายวิธีเปิดใช้เลเยอร์ในแต่ละแอป

  1. ใช้การตั้งค่าเชลล์ adb เพื่อเปิดใช้เลเยอร์:

    $ adb shell settings put global enable_gpu_debug_layers 1
    
  2. ระบุแอปพลิเคชันเป้าหมายเพื่อเปิดใช้เลเยอร์บน:

    $ adb shell settings put global gpu_debug_app <package_name>
    
  3. ระบุรายการเลเยอร์ที่จะเปิดใช้งาน (จากบนลงล่าง) โดยแยกแต่ละเลเยอร์ เลเยอร์ด้วยเครื่องหมายทวิภาค:

    $ adb shell settings put global gpu_debug_layers <layer1:layer2:layerN>
    

    เนื่องจากเรามีเลเยอร์การตรวจสอบ Khronos ชั้นเดียว คำสั่งจึงอาจ มีลักษณะดังนี้

    $ adb shell settings put global gpu_debug_layers VK_LAYER_KHRONOS_validation
    
  4. ระบุแพ็กเกจอย่างน้อยหนึ่งแพ็กเกจเพื่อค้นหาเลเยอร์ภายใน:

    $ 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>