本內容多借鑑Vulkan編程指南書籍內容。
創建Vulkan實例,與Vulkan打交道,通常的步驟是創建一個intance去初始化Vulkan library。這個instance是您的應用程序與Vulkan庫之間的連接橋樑,通常創建過程中,需要向驅動程序提供一些應用層的信息。
在本部分,我們開始使用 Vulkan API 編寫代碼。
代碼基本結構
class HelloTriangleApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
void initWindow() {}
void initVulkan() {}
void mainLoop() {}
void cleanup() {}
};
int main() {
HelloTriangleApplication app;
try {
app.run();
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
一、創建一個實例
Vulkan API使用vkInstance對象來存儲所有每個應用的狀態。應用程序必須在執行任何其他Vulkan操作之前創建一個Vulkan實例。
下面開始創建vulkan實例,首先添加一個createInstance函數,並在initVulkan函數中調用。
void initVulkan() {
createInstance();
}
另外添加一個類成員來保存instance句柄:
private:
VkInstance instance;
現在我們創建一個instance,並且爲該數據結構賦予自定義應用程序的信息。這些數據從技術角度是可選擇的,但是它可以爲驅動程序提供一些有用的信息來優化程序特殊的使用情景,比如驅動程序使用一些圖形引擎的特殊行爲。這個數據結構稱爲VkApplicationInfo:
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;appInfo.pNext = nullptr;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
如前所述,Vulkan中的許多數據結構要求在sType成員中明確的指定類型。pNext成員可用於指向特定的擴展結構。我們在這裏使用默認初始化,將其設置爲nullptr。
Vulkan中的大量信息通過結構體而不是函數參數傳遞,我們將填充一個結構體以提供足夠的信息創建instance。下一個結構體不是可選的,它需要告知Vulkan驅動程序我們需要使用哪些全局的 extensions 和 validation layers(下部分介紹校驗層)。這裏的全局意味着它適用於整個程序,而不是特定的設備,這些內容將在接下來的小節中說明。
VkInstanceCreateInfo需要填寫的信息如下:
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
前幾個參數比較簡單。接下來的兩個指定需要的全局擴展,Vulakn對於平臺特性是零API支持的(至少暫時這樣),這意味着需要一個擴展才能與不同平臺的窗體系統進行交互。GLFW有一個方便的內置函數,返回它有關的擴展信息,我們可以傳遞給struct:
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
結構體的最後兩個成員確定需要開啓的全局的validation layers。我們將會在下一節中深入探討這部分內容,在這一節設置爲空。
createInfo.enabledLayerCount = 0;
我們現在已經指定了Vulkan創建一個實例需要的一切信息,調用vkCreateInstance創建屬於我們的第一個instance:
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
如你所見,Vulkan中創建、實例化相關的函數參數一般遵循如下原則定義:
使用有關creation info 的結構體指針;
使用自定義分配器回調的指針;
使用保存新對象句柄的指針;
如果一切順利,此刻instance的句柄應該存儲在VkInstance類成員中了。幾乎所有的Vulkan函數都返回一個值爲VK_SUCCESS或錯誤代碼的VkResult類型的值。要檢查instance是否已經成功創建,我們不需要保存結果,僅僅使用 VK_SUCCESS 值來檢測即可,
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
二、檢查可選功能
如果你查看vkCreateInstance的文檔,你會看到一個可能出現的錯誤代碼是VK_ERROR_EXTENSION_NOT_PRESENT。我們可以簡單地指定我們需要的擴展,如果該錯誤代碼返回,則終止它們。這對於窗體系統或者諸如此類的擴展是有意義的,那麼如何檢查可選功能呢?
在創建instance之前檢索支持的擴展列表,通過vkEnumerateInstanceExtensionProperties函數。它指向一個變量,該變量存儲擴展數量和一個VkExtensionProperties數組來存儲擴展的詳細信息。它也接受一個可選擇的參數,允許我們通過特定的validation layers過濾擴展,現在我們暫時忽略這些。
要分配一個數組來保存擴展的詳細信息,我們首先需要知道有多少個擴展存在。可以通過將後一個參數置空來獲取擴展數量:
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
現在我們分配一個集合去持有擴展的詳細信息(include )
std::vector<VkExtensionProperties> extensions(extensionCount);
最後我們可以遍歷擴展的詳細信息:
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
每個VkExtensionProperties結構體包含擴展的名稱和版本。我們可以用簡單的for循環打印他們(\t是縮進)
std::cout << "available extensions:" << std::endl;
for (const auto& extension : extensions) {
std::cout << "\t" << extension.extensionName << std::endl;
}
如果需要獲取有關Vulkan支持的一些詳細信息,可以將此代碼添加到createInstance函數。作爲一個嘗試,創建一個函數,檢查glfwGetRequiredInstanceExtensions返回的所有擴展是否都包含在受支持的擴展列表中。
三、釋放內存
在程序退出前,請正確銷燬VkInstance。這部分可以定義在cleanup函數中,調用vkDestroyInstance函數完成。
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
vkDestroyInstance函數的參數很簡單。像之前小節提到的,Vulkan中的分配和釋放功能有一個可選的分配器回調,我們通過將nullptr設置忽略。後續小節中創建的所有Vulkan相關資源,集中在cleanup函數中進行清理,且確保在銷燬instance之前銷燬。
以上順利無誤的話,運行可看到如下窗口內容:
在進行更復雜的內容之前,下一部分了解下validation layers。
附:源代碼
// 01_instance_creation.cpp
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <vector>
const int WIDTH = 800;
const int HEIGHT = 600;
class HelloTriangleApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
GLFWwindow* window;
VkInstance instance;
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "HelloTriangle", nullptr, nullptr);
}
void initVulkan() {
createInstance();
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
void createInstance() {
//可選功能打印
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
std::cout << "available extensions:" << std::endl;
for (const auto& extension : extensions) {
std::cout << "\t" << extension.extensionName << std::endl;
}
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
createInfo.enabledLayerCount = 0;
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
}
};
int main() {
HelloTriangleApplication app;
try {
app.run();
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}