關于校驗層:
Vulkan API 的設計是緊緊圍繞最小化驅動程式開銷進行的,是以,預設情況下,Vulkan API 提供的錯誤檢查功能非常有限。很多很基本的錯誤都沒有被 Vulkan 顯式地處理,遇到錯誤程式會直接崩潰或者發生未被明确定義的行為。Vukan 需要我們顯式地定義每一個操作,是以就很容易在使用過程中産生一些小錯誤,比如使用了一個新的 GPU 特性,卻忘記在邏輯裝置建立時請求這一特性。
然而,這并不意味着我們不能将錯誤檢查加入 API 調用。Vulkan 引入了校驗層來優雅地解決這個問題。校驗層是一個可選的可以用來在 VulkanAPI 函數調用上進行附加操作的元件。
校驗層常被用來做下面的工作:
• 檢測參數值是否合法
• 追蹤對象的建立和清除操作,發現資源洩漏問題
• 追蹤調用來自的線程,檢測是否線程安全。
• 将 API 調用和調用的參數寫入日志
• 追蹤 API 調用進行分析和回放
可以使用這個校驗層實作來保證自己的應用程式在不同的驅動程式下能夠盡可能得表現一緻,而不是
依賴于某個驅動程式的未定義行為。
Vulkan 可以使用兩種不同類型的校驗層:執行個體校驗層和裝置校驗層。
執行個體校驗層隻檢查和全局 Vulkan 對象相關的調用,比如 Vulkan 執行個體。
裝置校驗層隻檢查和特定 GPU 相關的調用。裝置校驗層現在已經不推薦使用,也就是說,應該使用執行個體校驗層來檢測所有的 Vulkan 調用。
Vulkan規範文檔為了相容性仍推薦啟用裝置校驗層。
在本教程,為了簡便,我們為執行個體和裝置指定相同的校驗層
建立一個VkInstance執行個體包含以下内容
1.初始化 Vulkan 庫
2.指定了一些驅動程式需要使用的應用程式資訊
3.檢測擴充支援
4.設定回調函數來接受調試資訊
示例如下:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>//GLFW 庫會自動包含Vulkan 庫的頭檔案
#include <iostream>
#include <stdexcept>
#include <functional>//用于資源管理
#include <cstdlib>//用來使用 EXITSUCCESS 和 EXIT_FAILURE 宏
#include <set> //使用集合
#include <fstream>//讀取檔案
const int WIDTH = 800;
const int HEIGHT = 600;
//指定校驗層的名稱--代表隐式地開啟所有可用的校驗層
const std::vector<const char*> validataionLayers = {
"VK_LAYER_LUNARG_standard_validation"
};
//控制是否啟用指定的校驗層
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
class HelloTriangle{
public:
void run(){
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
GLFWwindow* window = nullptr;//視窗句柄
VkInstance instance;//vulkan執行個體句柄
VkDebugUtilsMessengerEXT callback;//存儲回調函數資訊
///初始化glfw
void initWindow(){
glfwInit();//初始化glfw庫
//顯示阻止自動建立opengl上下文
glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
//禁止視窗大小改變
glfwWindowHint(GLFW_RESIZABLE,GLFW_FALSE);
/**
glfwCreateWindow 函數:
前三個參數指定了要建立的視窗的寬度,高度和标題.
第四個參數用于指定在哪個顯示器上打開視窗,
最後一個參數與 OpenGL 相關
*/
//建立視窗
window = glfwCreateWindow(WIDTH,HEIGHT,"vulakn",
nullptr,nullptr);
}
void createInstance(){
//是否啟用校驗層并檢測指定的校驗層是否支援
if(enableValidationLayers && !checkValidationLayerSupport()){
throw std::runtime_error(
"validation layers requested,but not available");
}
/**
VkApplicationInfo設定寫應用程式資訊,這些資訊的填寫不是必須的,但填寫的資訊
可能會作為驅動程式的優化依據,讓驅動程式進行一些特殊的優化。比如,應用程式使用了
某個引擎,驅動程式對這個引擎有一些特殊處理,這時就可能有很大的優化提升。
*/
/**
Vulkan 建立對象的一般形式如下:
sType 成員變量來顯式指定結構體類型
pNext 成員可以指向一個未來可能擴充的參數資訊--這個教程裡不使用
*/
VkApplicationInfo appinfo={};
appinfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appinfo.pApplicationName = "hello";
appinfo.applicationVersion = VK_MAKE_VERSION(1,1,77);
appinfo.pEngineName = "No Engine";
appinfo.engineVersion = VK_MAKE_VERSION(1,1,77);
appinfo.apiVersion = VK_API_VERSION_1_1;
/**
VkInstanceCreateInfo告訴Vulkan的驅動程式需要使用的全局擴充和校驗層
全局是指這裡的設定對于整個應用程式都有效,而不僅僅對一個裝置有效
*/
//設定vulkan執行個體資訊
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appinfo;
/**
傳回支援的擴充清單:
我們可以擷取擴充的個數,以及擴充的詳細資訊
*/
uint32_t extensionCount = 0;//擴充的個數
vkEnumerateInstanceExtensionProperties(nullptr,
&extensionCount,nullptr);
//配置設定數組來存儲擴充資訊
//每個 VkExtensionProperties 結構體包含了擴充的名字和版本資訊
std::vector<VkExtensionProperties> extensions(extensionCount);
//擷取所有擴充資訊
vkEnumerateInstanceExtensionProperties(nullptr,
&extensionCount,
extensions.data());
std::cout << "available extension:" << std::endl;
for(const auto& extension : extensions){
std::cout << "\t"<<extension.extensionName<<std::endl;
}
//設定擴充清單
auto extensions2 = getRequiredExtensions();
createInfo.enabledExtensionCount =
static_cast<uint32_t>(extensions2.size());
createInfo.ppEnabledExtensionNames = extensions2.data();
//判斷是否啟用校驗層,如果啟用則設定校驗層資訊
if(enableValidationLayers){
//設定layer資訊
createInfo.enabledLayerCount =
static_cast<uint32_t>(validataionLayers.size());
createInfo.ppEnabledLayerNames = validataionLayers.data();
}else{
createInfo.enabledLayerCount = 0;
}
/**
建立 Vulkan 對象的函數參數的一般形式如下:
1.一個包含了建立資訊的結構體指針
2.一個自定義的配置設定器回調函數,本教程未使用,設定為nullptr
3.一個指向新對象句柄存儲位置的指針
*/
//建立vulkan執行個體
VkResult result = vkCreateInstance(&createInfo,nullptr,
&instance);
if(result != VK_SUCCESS){
throw std::runtime_error("failed to create instance!");
}
}
//使用代理函數建立VkDebugUtilsMessengerEXT
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance ,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks * pAllocator,
VkDebugUtilsMessengerEXT* pCallback){
/**
vkCreateDebugUtilsMessengerEXT 函數是一個擴充函數,不會被Vulkan庫
自動加載,是以需要我們自己使用 vkGetInstanceProcAddr 函數來加載它
函數的第二個參數是可選的配置設定器回調函數,我們沒有自定義的配置設定器,
是以将其設定為 nullptr。由于我們的調試回調是針對特定Vulkan執行個體和它的校驗層,
是以需要在第一個參數指定調試回調作用的 Vulkan 執行個體。
*/
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
instance,"vkCreateDebugUtilsMessengerEXT");
if ( func != nullptr ){
//使用代理函數來建立擴充對象
return func(instance,pCreateInfo,pAllocator,pCallback);
}else{
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
//建立代理函數銷毀VkDebugUtilsMessengerEXT
void DestroyDebugUtilsMessengerEXT(VkInstance instance ,
VkDebugUtilsMessengerEXT callback,
const VkAllocationCallbacks* pAllocator){
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
instance,"vkDestroyDebugUtilsMessengerEXT");
if ( func != nullptr ){
return func(instance,callback,pAllocator);
}
}
//設定調試回調
void setupDebugCallback(){
//如果未啟用校驗層直接傳回
if(!enableValidationLayers)
return;
//設定調試結構體所需的資訊
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
//使用代理函數建立 VkDebugUtilsMessengerEXT 對象
if(CreateDebugUtilsMessengerEXT(instance,&createInfo,
nullptr,&callback) != VK_SUCCESS){
throw std::runtime_error("faild to set up debug callback!");
}
}
//初始化 Vulkan 對象。
void initVulkan(){
createInstance();//建立vulkan執行個體
setupDebugCallback();//調試回調
}
//設定主循環
void mainLoop(){
//添加事件循環
//glfwWindowShouldClose檢測視窗是否關閉
while(!glfwWindowShouldClose(window)){
glfwPollEvents();//執行事件處理
}
}
//清理資源
void cleanup(){
if(enableValidationLayers){
//調用代理銷毀VkDebugUtilsMessengerEXT對象
DestroyDebugUtilsMessengerEXT(instance,callback,nullptr);
}
/**
Vulkan 中建立和銷毀對象的函數都有一個 VkAllocationCallbacks 參數,
可以被用來自定義記憶體配置設定器,本教程也不使用
*/
//銷毀vulkan執行個體
vkDestroyInstance(instance,nullptr);
//銷毀視窗
glfwDestroyWindow(window);
//結束glfw
glfwTerminate();
}
//請求所有可用的校驗層
bool checkValidationLayerSupport(){
//vkEnumerateInstanceLayerProperties擷取了所有可用的校驗層列
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount,nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount,
availableLayers.data());
for(const char* layerName : validataionLayers){
bool layerFound = false;
for(const auto& layerProperties : availableLayers){
std::cout << "layername:"<<layerProperties.layerName<<std::endl;
if(strcmp(layerName,layerProperties.layerName)==0){
layerFound = true;
break;
}
}
if(!layerFound){
return false;
}
}
return true;
}
//根據是否啟用校驗層,傳回所需的擴充清單
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){
//需要使用 VK_EXT_debug_utils 擴充,設定回調函數來接受調試資訊
//如果啟用校驗層,添加調試報告相關的擴充
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
/**
第一個參--指定了消息的級别,可以使用比較運算符來過濾處理一定級别以上的調試資訊
它可以是下面的值:
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:診斷資訊
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:資源建立之類的資訊
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:警告資訊
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:不合法和可能造成崩潰的操作資訊
第二個參數--消息的類型,如下:
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
發生了一些與規範和性能無關的事件
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
出現了違反規範的情況或發生了一個可能的錯誤
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
進行了可能影響 Vulkan 性能的行為
第三個參--一個指向 VkDebugUtilsMessengerCallbackDataEXT 結構體的指針
包含了下面這些非常重要的成員:
pMessage:一個以 null 結尾的包含調試資訊的字元串
pObjects:存儲有和消息相關的 Vulkan 對象句柄的數組
objectCount:數組中的對象個數
最後一個參數 pUserData 是一個指向了我們設定回調函數時,傳遞的資料的指針
回調函數傳回了一個布爾值,用來表示引發校驗層處理的 Vulkan API調用是否被中斷。
如果傳回值為 true,對應 Vulkan API 調用就會傳回
VK_ERROR_VALIDATION_FAILED_EXT 錯誤代碼。
通常,隻在測試校驗層本身時會傳回 true,其餘情況下,回調函數應該傳回 VK_FALSE
*/
//接受調試資訊的回調函數,以 vkDebugUtilsMessengerCallbackEXT 為原型
//使用 VKAPI_ATTR 和 VKAPI_CALL 定義,確定它可以被 Vulkan 庫調用
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;
}
};
int main(int argc, char *argv[])
{
HelloTriangle hello;
try{
hello.run();
}catch(const std::exception& e){
//捕獲并列印hello中抛出的異常
std::cerr<<e.what()<<std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Vulkan API使用該vkInstance對象存儲所有每個應用程式狀态。應用程式必須在執行任何其他Vulkan操作之前建立Vulkan執行個體。
基本的Vulkan架構如下所示:
上圖顯示Vulkan應用程式連結到Vulkan庫,通常稱為加載器。建立執行個體會初始化加載器。加載程式還加載并初始化低級圖形驅動程式,通常由GPU硬體供應商提供。
請注意,此圖中描述了各個層,這些層也由加載程式加載。層通常用于驗證,這是通常由驅動程式執行的錯誤檢查。在Vulkan中,驅動程式比其他API(如OpenGL)更輕量級,部分原因是它們将此驗證功能委托給驗證層。圖層是可選的,每次應用程式建立執行個體時都可以有選擇地加載圖層。
建立執行個體
#define APP_SHORT_NAME "vulkansamples_instance"
int main(int argc, char *argv[])
{
/* VULKAN_KEY_START */
//初始化有關應用程式的一些基本資訊
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pNext = NULL;
app_info.pApplicationName = APP_SHORT_NAME;
app_info.applicationVersion = 1;
app_info.pEngineName = APP_SHORT_NAME;
app_info.engineVersion = 1;
app_info.apiVersion = VK_API_VERSION_1_1;
//建立執行個體所需的附加資訊
VkInstanceCreateInfo inst_info = {};
inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;//結構的類型
//此void指針有時用于在類型結構中傳遞特定于擴充的資訊,其中sType成員設定為擴充定義的值。
//如上所述,擴充可以分析沿着這個pNext指針鍊傳遞的任何結構,以找到它們識别的結構
inst_info.pNext = NULL;
//目前沒有定義标志,是以将其設定為零
inst_info.flags = 0;
inst_info.pApplicationInfo = &app_info;
inst_info.enabledExtensionCount = 0;//擴充個數
inst_info.ppEnabledExtensionNames = NULL;
inst_info.enabledLayerCount = 0;//層個數
inst_info.ppEnabledLayerNames = NULL;
VkInstance inst;// 如果執行個體建立成功,函數傳回的句柄
VkResult res;
//應用程式執行自己的主機記憶體管理,否則,Vulkan實作使用預設的系統記憶體管理工具
VkAllocationCallbacks* callback = NULL;
//傳回成功res為0
res = vkCreateInstance(&inst_info, callback, &inst);
if (res == VK_ERROR_INCOMPATIBLE_DRIVER) {
std::cout << "cannot find a compatible Vulkan ICD\n";
exit(-1);
} else if (res) {
std::cout << "unknown error\n";
exit(-1);
}
vkDestroyInstance(inst, NULL);//銷毀該執行個體
/* VULKAN_KEY_END */
return 0;
}