Posting ini adalah kelanjutan dari posting sebelumnya " Vulkan. Panduan Pengembang. Menggambar Segitiga ", ini didedikasikan untuk terjemahan lapisan Validasi bab.
Kandungan
Lapisan validasi
- Apa itu lapisan validasi?
- Menggunakan lapisan validasi
- Intersepsi pesan debug
- Debugging instance Vulkan (instance debugging)
- Menguji
- Pengaturan
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 diagnostikVK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
: pesan informasional, misalnya, tentang membuat sumber dayaVK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
: pesan tentang perilaku yang belum tentu salah, tetapi kemungkinan besar menunjukkan kesalahanVK_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 kinerjaVK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
: peristiwa yang terjadi melanggar spesifikasi atau menunjukkan kemungkinan kesalahanVK_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 nullpObjects
: larik deskriptor objek yang terkait dengan pesanobjectCount
: 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 ++