Vulkan. Panduan pengembang. Lapisan validasi

Saya seorang penerjemah dari CG Tribe di Izhevsk, dan di sini saya membagikan terjemahan manual API Vulkan. Tautan sumber - vulkan-tutorial.com .



Posting ini adalah kelanjutan dari posting sebelumnya " Vulkan. Panduan Pengembang. Menggambar Segitiga ", ini didedikasikan untuk terjemahan lapisan Validasi bab.



Kandungan
1.



2.



3.



4.





  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ







Lapisan validasi







Apa itu lapisan validasi?



Desain Vulkan API didasarkan pada gagasan tentang beban minimum pada driver, jadi secara default, kemampuan deteksi kesalahan sangat terbatas. Bahkan kesalahan sederhana seperti nilai yang salah dalam pencacahan atau meneruskan petunjuk nol biasanya tidak ditangani secara eksplisit dan menyebabkan crash atau perilaku yang tidak ditentukan. Karena bekerja dengan Vulkan memerlukan deskripsi mendetail dari setiap tindakan, kesalahan semacam itu bisa cukup sering terjadi.



Untuk mengatasi masalah ini, Vulkan menggunakan lapisan validasi . Lapisan validasi adalah komponen opsional yang dapat dipasang ke pemanggilan fungsi untuk melakukan operasi tambahan. Operasi berikut dapat dilakukan di lapisan validasi:



  • Memeriksa nilai parameter sesuai spesifikasi untuk mendeteksi kesalahan
  • Pelacakan Kebocoran Sumber Daya
  • Pemeriksaan keamanan streaming
  • Pencatatan setiap panggilan dan parameternya
  • Pelacakan Panggilan Vulkan untuk Pembuatan Profil dan Putar Ulang


Di bawah ini adalah contoh bagaimana sebuah fungsi dapat diimplementasikan di lapisan validasi:



VkResult vkCreateInstance(
    const VkInstanceCreateInfo* pCreateInfo,
    const VkAllocationCallbacks* pAllocator,
    VkInstance* instance) {

    if (pCreateInfo == nullptr || instance == nullptr) {
        log("Null pointer passed to required parameter!");
        return VK_ERROR_INITIALIZATION_FAILED;
    }

    return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}
      
      





Anda dapat menggabungkan lapisan validasi satu sama lain untuk menggunakan semua fitur debug yang Anda butuhkan. Selain itu, lapisan validasi dapat diaktifkan untuk build debug dan dinonaktifkan sepenuhnya untuk build rilis, yang sangat memudahkan.



Vulkan tidak memiliki lapisan validasi bawaan, tetapi SDK Vulkan dari LunarG menyediakan serangkaian lapisan yang baik untuk melacak bug yang paling umum. Semua lapisan adalah sumber terbuka , dan Anda selalu dapat melihat bug apa yang mereka lacak. Berkat lapisan validasi, Anda dapat menghindari kesalahan pada driver berbeda yang terkait dengan perilaku tidak terdefinisi.



Untuk menggunakan lapisan validasi, lapisan tersebut harus diinstal di sistem. Misalnya, lapisan validasi LunarG hanya tersedia jika Vulkan SDK diinstal.



Sebelumnya, Vulkan memiliki dua jenis lapisan validasi: khusus instance dan khusus perangkat. Intinya adalah lapisan instance memeriksa panggilan yang terkait dengan objek Vulkan global, sedangkan lapisan perangkat hanya memeriksa panggilan yang terkait dengan GPU tertentu. Pada tahap ini, lapisan perangkat sudah tidak digunakan lagi, jadi lapisan validasi instance diterapkan ke semua panggilan Vulkan. Spesifikasi tersebut masih merekomendasikan penyertaan lapisan validasi tingkat perangkat, termasuk untuk memberikan kompatibilitas, yang diperlukan untuk beberapa implementasi. Kami akan menentukan lapisan yang sama untuk instance dan perangkat logis, yang akan kita pelajari nanti.



Menggunakan lapisan validasi



Di bagian ini, kita akan melihat cara menghubungkan lapisan yang disediakan oleh Vulkan SDK. Selain untuk ekstensi, kita harus menentukan nama-nama layer untuk menghubungkannya. Semua pemeriksaan yang berguna bagi kami dikumpulkan dalam lapisan bernama " VK_LAYER_KHRONOS_validation



".



Mari tambahkan dua konstanta konfigurasi. Yang pertama (validationLayers) akan mencantumkan lapisan validasi mana yang ingin kita sertakan. Yang kedua (enableValidationLayers) akan memungkinkan koneksi tergantung pada mode build. Makro ini NDEBUG



adalah bagian dari standar C ++ dan singkatan dari "not debug".



const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

#ifdef NDEBUG
    const bool enableValidationLayers = false;
#else
    const bool enableValidationLayers = true;
#endif
      
      





Mari tambahkan fungsi baru checkValidationLayerSupport



yang akan memeriksa apakah semua lapisan yang diperlukan tersedia. Pertama, mari kita dapatkan daftar lapisan yang tersedia menggunakan vkEnumerateInstanceLayerProperties



. Penggunaannya mirip dengan fungsi yang vkEnumerateInstanceExtensionProperties



kita lihat sebelumnya.



bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

    return false;
      
      





Setelah itu, periksa apakah semua lapisan dari validationLayers



ada di availableLayers



. Anda mungkin perlu terhubung <cstring>



ke strcmp



.



for (const char* layerName : validationLayers) {
    bool layerFound = false;

    for (const auto& layerProperties : availableLayers) {
        if (strcmp(layerName, layerProperties.layerName) == 0) {
            layerFound = true;
            break;
        }
    }

    if (!layerFound) {
        return false;
    }
}

return true;
      
      





Fungsi tersebut sekarang dapat digunakan di createInstance



:



void createInstance() {
    if (enableValidationLayers && !checkValidationLayerSupport()) {
        throw std::runtime_error("validation layers requested, but not available!");
    }

    ...
}
      
      





Jalankan program dalam mode debug dan pastikan tidak ada kesalahan.



Dalam struktur, VkInstanceCreateInfo



tentukan nama dari lapisan validasi yang terhubung:



if (enableValidationLayers) {
    createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
    createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
    createInfo.enabledLayerCount = 0;
}
      
      





Jika pemeriksaan kami berhasil, vkCreateInstance



seharusnya tidak mengembalikan kesalahan VK_ERROR_LAYER_NOT_PRESENT



, tetapi lebih baik memverifikasi ini dengan menjalankan program.



Intersepsi pesan debug



Secara default, lapisan validasi mengirim pesan debug ke keluaran standar, tetapi Anda dapat menanganinya sendiri dengan menyediakan fungsi callback. Ini akan memungkinkan Anda untuk memfilter pesan yang ingin Anda terima, karena tidak semuanya berisi peringatan kesalahan. Jika Anda ingin melewatkan langkah ini, langsung ke bagian terakhir dari bab ini.



Untuk menghubungkan fungsi panggilan balik untuk memproses pesan, Anda perlu mengkonfigurasi utusan debug menggunakan VK_EXT_debug_utils



.



Pertama, mari tambahkan fungsi getRequiredExtensions



yang akan mengembalikan daftar ekstensi yang diperlukan tergantung pada apakah lapisan validasi terhubung atau tidak.



std::vector<const char*> getRequiredExtensions() {
    uint32_t glfwExtensionCount = 0;
    const char** glfwExtensions;
    glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

    std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

    if (enableValidationLayers) {
        extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
    }

    return extensions;
}
      
      





Ekstensi GLFW diperlukan, dan ekstensi debug messenger ditambahkan berdasarkan kondisi. Harap perhatikan bahwa kami menggunakan makro VK_EXT_DEBUG_UTILS_EXTENSION_NAME



untuk menghindari kesalahan ketik.



Sekarang kita dapat menggunakan fungsi ini di createInstance



:



auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
      
      





Jalankan program untuk memeriksa apakah kami menerima kesalahan VK_ERROR_EXTENSION_NOT_PRESENT



.



Sekarang mari kita lihat apa fungsi panggilan balik itu sendiri. Mari tambahkan metode statis baru dengan prototipe PFN_vkDebugUtilsMessengerCallbackEXT



. VKAPI_ATTR



dan VKAPI_CALL



pastikan metode tersebut memiliki tanda tangan yang benar.



static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData) {

    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

    return VK_FALSE;
}
      
      





Parameter pertama menentukan tingkat keparahan pesan, yaitu:



  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT



    : pesan diagnostik
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT



    : pesan informasional, misalnya, tentang membuat sumber daya
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT



    : pesan tentang perilaku yang belum tentu salah, tetapi kemungkinan besar menunjukkan kesalahan
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT



    : pesan tentang perilaku tidak benar yang dapat menyebabkan crash


Nilai pencacahan dipilih sedemikian rupa sehingga Anda dapat menggunakan operasi perbandingan untuk menyaring pesan di atas atau di bawah beberapa ambang batas, misalnya:



if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
    // Message is important enough to show
}
      
      





Parameter messageType



dapat memiliki nilai berikut:



  • VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT



    : peristiwa yang terjadi tidak terkait dengan spesifikasi atau kinerja
  • VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT



    : peristiwa yang terjadi melanggar spesifikasi atau menunjukkan kemungkinan kesalahan
  • VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT



    : Vulkan mungkin tidak dapat digunakan secara optimal


Parameter pCallbackData



mengacu pada struktur VkDebugUtilsMessengerCallbackDataEXT



yang berisi detail pesan. Anggota terpenting dari struktur tersebut adalah:



  • pMessage



    : pesan debug sebagai string yang diakhiri dengan null
  • pObjects



    : larik deskriptor objek yang terkait dengan pesan
  • objectCount



    : jumlah objek dalam array


Parameter pUserData



berisi penunjuk yang diteruskan selama penyiapan fungsi panggilan balik.



Fungsi callback mengembalikan VkBool32



tipe. Hasilnya menunjukkan apakah akan menghentikan panggilan yang menghasilkan pesan tersebut. Jika fungsi panggilan balik kembali VK_TRUE



, panggilan dibatalkan dan kode kesalahan dikembalikan VK_ERROR_VALIDATION_FAILED_EXT



. Sebagai aturan, ini hanya terjadi ketika menguji lapisan validasi itu sendiri, dalam kasus kami, Anda perlu kembali VK_FALSE



.



Tetap memberi tahu Vulkan tentang fungsi callback. Anehnya, bahkan mengontrol fungsi callback debug di Vulkan memerlukan deskriptor yang harus dibuat dan dimusnahkan secara eksplisit. Fungsi panggilan balik ini adalah bagian dari utusan debug, dan jumlahnya tidak terbatas. Tambahkan anggota kelas untuk deskriptor setelah instance



:



VkDebugUtilsMessengerEXT debugMessenger;
      
      





Sekarang tambahkan fungsi yang setupDebugMessenger



akan dipanggil dari initVulkan



kanan setelah createInstance



:



void initVulkan() {
    createInstance();
    setupDebugMessenger();
}

void setupDebugMessenger() {
    if (!enableValidationLayers) return;

}
      
      





Kita perlu mengisi struktur dengan detail tentang messenger dan fungsi callbacknya:



VkDebugUtilsMessengerCreateInfoEXT createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional
      
      





Bidang ini messageSeverity



memungkinkan Anda untuk menentukan tingkat keparahan yang akan dipanggil untuk fungsi callback. Kami mengatur semua derajat, kecuali VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT



untuk diberitahu tentang kemungkinan masalah dan tidak mengacaukan konsol dengan informasi debugging yang mendetail.



Demikian pula, bidang ini messageType



memungkinkan Anda memfilter pesan menurut jenisnya. Kami telah memilih semua jenis, tetapi Anda selalu dapat menonaktifkan yang tidak perlu. Penunjuk ke fungsi panggilan balik diteruskan ke



lapangan pfnUserCallback



. Secara opsional, Anda bisa meneruskan pointer ke lapangan pUserData



, itu akan diteruskan ke fungsi callback melalui parameter pUserData



.



Perhatikan bahwa ada cara lain untuk menyesuaikan pesan lapisan validasi dan debug callback, tetapi ini adalah cara terbaik untuk memulai Vulkan. Untuk informasi lebih lanjut tentang metode lain, lihat spesifikasi ekstensi .



Struktur harus diteruskan ke fungsi vkCreateDebugutilsMessengerEXT



untuk membuat objek VkDebugUtilsMessengerEXT



. Ini adalah fitur ekstensi, jadi tidak dimuat secara otomatis. Anda perlu mencari alamatnya sendiri menggunakan vkGetInstanceProcAddr



. Kami akan membuat fungsi proxy kami sendiri yang akan melakukan ini secara internal. Tambahkan sebelum definisi kelas HelloTriangleApplication



.



VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}
      
      





Kami menggunakan fungsi ini untuk membuat messenger:



if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
    throw std::runtime_error("failed to set up debug messenger!");
}
      
      





Parameter kedua dari belakang adalah opsional, ini adalah fungsi panggilan balik dari pengalokasi, yang akan kita tentukan sebagai nullptr



. Parameter lainnya cukup sederhana. Karena messenger digunakan untuk instance Vulkan tertentu (dan lapisan validasinya), penunjuk ke instance ini harus diteruskan sebagai argumen pertama. Kami akan menemukan pola ini untuk objek anak lainnya.



Objek VkDebugUtilsMessengerEXT



harus dihancurkan dengan memanggil vkDestroyDebugUtilsMessengerEXT



. Selain itu vkCreateDebugUtilsMessengerEXT



, kita harus memuat fungsi ini secara eksplisit.



Kemudian CreateDebugUtilsMessengerEXT



buat fungsi proxy lain:



void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}
      
      





Periksa apakah fungsi ini merupakan fungsi statis kelas atau fungsi di luar kelas. Setelah itu, dapat dipanggil dalam sebuah fungsi cleanup



:



void cleanup() {
    if (enableValidationLayers) {
        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
    }

    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}
      
      







Debugging instance Vulkan (instance debugging)



Kami telah menambahkan debugging dengan lapisan validasi, tetapi masih ada sedikit lagi. Sebuah vkCreateDebugUtilsMessengerEXT



contoh yang valid vkDestroyDebugUtilsMessengerEXT



diperlukan untuk panggilan , dan harus dipanggil sebelum contoh ini hancur. Oleh karena itu, kita tidak bisa debug di vkCreateInstance



dan belum vkDestroyInstance



.



Namun, jika Anda membaca spesifikasinya dengan hati - hati , Anda akan melihat bahwa dimungkinkan untuk membuat utusan debug terpisah untuk kedua fungsi ini. Untuk melakukan ini, Anda perlu menyetel penunjuk pNext



struktur VkInstanceCreateInfo



ke struktur VkDebugUtilsMessengerCreateInfoEXT



. Pertama, mari kita pindahkan pengisian VkDebugUtilsMessengerCreateInfoEXT



ke metode terpisah:



void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
    createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
    createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
    createInfo.pfnUserCallback = debugCallback;
}

...

void setupDebugMessenger() {
    if (!enableValidationLayers) return;

    VkDebugUtilsMessengerCreateInfoEXT createInfo;
    populateDebugMessengerCreateInfo(createInfo);

    if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
        throw std::runtime_error("failed to set up debug messenger!");
    }
}
      
      





Kami dapat menggunakannya kembali dalam suatu fungsi createInstance



:



void createInstance() {
    ...

    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;

    ...

    VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
    if (enableValidationLayers) {
        createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
        createInfo.ppEnabledLayerNames = validationLayers.data();

        populateDebugMessengerCreateInfo(debugCreateInfo);
        createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
    } else {
        createInfo.enabledLayerCount = 0;

        createInfo.pNext = nullptr;
    }

    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
        throw std::runtime_error("failed to create instance!");
    }
}
      
      





Variabel debugCreateInfo



berada di luar pernyataan if sehingga tidak dihancurkan sebelum dipanggil vkCreateInstance



. Membuat utusan debug tambahan dengan cara ini memungkinkan Anda menggunakannya secara otomatis vkCreateInstance



dan vkDestroyInstance



, setelah itu akan dimusnahkan.



Menguji



Mari sengaja membuat kesalahan untuk melihat lapisan validasi beraksi.

Hapus sementara panggilan DestroyDebugUtilsMessengerEXT



dalam fungsi cleanup



dan jalankan program. Anda akan mendapatkan hasil seperti ini:







Untuk mengetahui panggilan mana yang menghasilkan pesan yang dikirim, tambahkan breakpoint ke fungsi callback pesan dan lihat tumpukan panggilan.





Pengaturan



Ada lebih banyak penyesuaian yang mengatur perilaku tingkat validasi di luar yang ditentukan dalam struktur VkDebugUtilsMessengerCreateInfoEXT



. Buka Vulkan SDK dan buka direktori Config



. Di sana Anda akan menemukan file vk_layer_settings.txt



yang menjelaskan cara mengatur lapisan.



Untuk mengatur lapisan, menyalin file ke direktori Debug



dan Release



dan ikuti petunjuk untuk mengkonfigurasi perilaku yang diinginkan. Namun, di sepanjang panduan ini, diasumsikan bahwa Anda menggunakan pengaturan default.



Di masa mendatang, kami sengaja akan membuat kesalahan untuk menunjukkan kepada Anda betapa nyaman dan efektifnya menggunakan lapisan validasi untuk melacaknya.



Kode C ++



All Articles