Vulkan 開發的系列文章:git
在 Vulkan 的系列文章中出現過以下的圖片:github
這張圖片很詳細的歸納了 Vulkan 中的重要組件以及它們的工做流程,接下來的文章中會針對每一個組件進行學習講解並配上相關的示例代碼,首先是 Instance、Device 和 Queue 組件。api
在開始建立 Device 等組件以前,須要建立一個 VkInstance
對象。微信
經過 vkCreateInstance
方法建立 VKInstance
對象,如下是函數原型,在 <vulkan.h>
頭文件中。架構
// 聲明的函數指針的形式
typedef VkResult (VKAPI_PTR *PFN_vkCreateInstance) (const VkInstanceCreateInfo* pCreateInfo, // 提供建立的信息 const VkAllocationCallbacks* pAllocator, // 建立時的回調函數 VkInstance* pInstance); // 建立的實例
複製代碼
<vulkan.h> 的頭文件把函數經過 typedef
關鍵字聲明成了函數指針的形式,可能會有點難找。app
在 Vulkan 的 API 中有一些固定的 調用套路 。函數
- 要建立某個對象,先提供一個包含建立信息的對象。
- 建立時經過傳遞引用的方式來傳參。
接下來看看這個套路是如何應用在 VKInstance
對象上的。post
在 vkCreateInstance
函數中看到有個名爲 VkInstanceCreateInfo
類型的參數,這就是包含了 VKInstance
要建立的信息。性能
它的參數信息有點多:學習
typedef struct VkInstanceCreateInfo {
VkStructureType sType; // 通常爲方法對應的類型
const void* pNext; // 通常爲 null 就行了
VkInstanceCreateFlags flags; // 留着之後用的,設爲 0 就行了
const VkApplicationInfo* pApplicationInfo; // 對應新的一個結構體 VkApplicationInfo
uint32_t enabledLayerCount; // layer 和 extension 用於調試和拓展
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
} VkInstanceCreateInfo;
複製代碼
除了還須要建立一個 VkApplicationInfo
對象,還能夠設置 Layer
和 Extension
。
其中:Layer
是用來錯誤校驗、調試輸出的。爲了提供性能,其中的方法之一就是減小驅動進行狀態、錯誤校驗,而 Vulkan 就把這一層單獨抽出來了。
Layer
在整個架構中的位置如上圖,Vulkan API 直接和驅動對話,而 Layer
處於應用和 Vulkan API 之間,供開發者進行調試。
另外,Extension
就是 Vulkan 支持的拓展,最典型的就是 Vulkan 的跨平臺渲染顯示,就是經過拓展來完成的,好比在 Android、Windows 上使用 Vulkan 都須要使用不一樣的拓展才能夠把內容顯示到屏幕上。
關於 Layer
和 Extension
後續再細說。
接着回到 VkApplicationInfo
結構體,也是建立 Instance 的必要參數之一。
typedef struct VkApplicationInfo {
VkStructureType sType;
const void* pNext;
const char* pApplicationName;
uint32_t applicationVersion;
const char* pEngineName;
uint32_t engineVersion;
uint32_t apiVersion;
} VkApplicationInfo;
複製代碼
它的參數釋義就比較容易理解了,設置應用的名稱、版本號等,有了它們就能夠建立 Instance
對象了,代碼能夠參考 這裏 。
具體的代碼以下:
VkApplicationInfo app_info = {};
app_info.apiVersion = VK_API_VERSION_1_0;
app_info.applicationVersion = 1;
app_info.engineVersion = 1;
app_info.pNext = nullptr;
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pEngineName = APPLICATION_NAME;
app_info.pApplicationName = APPLICATION_NAME;
VkInstanceCreateInfo instance_info = {};
// type 就是結構體的類型
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_info.pNext = nullptr;
instance_info.pApplicationInfo = &app_info;
instance_info.flags = 0;
// Extension and Layer 暫時不用,可空
instance_info.enabledExtensionCount = 0;
instance_info.ppEnabledExtensionNames = nullptr;
instance_info.ppEnabledLayerNames = nullptr;
instance_info.enabledLayerCount = 0;
VkResult result = vkCreateInstance(&instance_info, nullptr, &instance);
複製代碼
當每調用一個建立函數後,返回的類型都是 VkResult
,只要 VkResult 大於 0 ,那麼執行就是成功的。
另外還有個參數是 VkAllocationCallbacks
,表示函數調用時的回調,須要傳遞一個函數指針,在後面的各類調用中都會看到它的身影,若是有用到能夠傳參,通常爲 nullptr
就行了。
關於每一個結構體,它每一個參數的具體釋義,靠死記硬背是確定不行的,參考 vkspec.pdf
書籍,裏面有對每一個參數、結構體的詳細釋義。
有了 Instance
組件,就能夠建立 Device
組件了,按照調用的套路,確定還會有一個 VkDeviceCreateInfo
的結構體表示 Device
的建立信息。
而 Device
具體指的是邏輯上的設備,能夠說是對物理設備的一個邏輯上的封裝,而物理設備就是 VkPhysicalDevice
對象。
在某些狀況下,可能會具備多個物理設備,以下圖所示,所以要先枚舉一下全部的物理設備:
LOGI("enumerate gpu device");
uint32_t gpu_size = 0;
// 第一次調用只爲了得到個數
VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_size, nullptr);
複製代碼
在 vkEnumeratePhysicalDevices
方法中,傳入的第二個參數爲 gpu 的個數,第三個參數爲 null,這樣的一次調用會返回 gpu 的個數到 gpu_size
變量。
vector<VkPhysicalDevice> gpus;
gpus.resize(gpu_size);
// vector.data() 方法轉換成指針類型
// 第二次調用得到全部的數據
res = vkEnumeratePhysicalDevices(instance, &gpu_size, gpus.data());
複製代碼
當再一次調用 vkEnumeratePhysicalDevices
函數時,第三個參數不爲 null,而是相應的 VkPhysicalDevice
容器,那麼 gpus 會填充 gpu_size
個的 VkPhysicalDevice
對象。
這也算是 Vulkan API 調用的一個 固定套路 了,調用兩次來得到數據,在後面的代碼中也會常常看到這種方式。
有了 VkPhysicalDevice
對象以後,能夠查詢 VkPhysicalDevice
上的一些屬性,如下函數均可以查詢相關信息:
在這裏須要用到的屬性是 QueueFamilyProperties
,得到該屬性的方法調用方式和得到 VkPhysicalDevice
數據方式同樣,也是一個兩次調用。
若是有設備有多個 GPU,那麼這裏取第一個來獲取它的相關屬性:
// 第一次調用,得到個數
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(gpus[0], &queue_family_count, nullptr);
assert(queue_family_count != 0);
// 第二次調用,得到實際數據
vector<VkQueueFamilyProperties> queue_family_props;
queue_family_props.resize(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(gpus[0], &queue_family_count, queue_family_props.data());
assert(queue_family_count != 0);
複製代碼
QueueFamilyProperties
的結構體含義以下:
typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags; // 標識位:表示 Queue 的功能
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
複製代碼
其中:queueFlags
表示該 Queue 的能力,有的 Queue 是用來渲染圖像的,這個和咱們的使用最爲密切,還有的 Queue 是用來計算的。
具體的 Flag 標識以下:
typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001, // 圖像相關
VK_QUEUE_COMPUTE_BIT = 0x00000002, // 計算相關
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkQueueFlagBits;
typedef VkFlags VkQueueFlags;
複製代碼
通常來講,咱們用的是 queueFlags
爲 VK_QUEUE_GRAPHICS_BIT
標識位的 Queue
。
那麼 Queue
到底是什麼?
物理設備可能會有多個 Queue
,不一樣的 Queue
對應不一樣的特性。
在文章最開始的圖中能夠看到,Command-buffer
是提交到了 Queue
,Queue
再提交給 Device
去執行。Queue
能夠當作是應用程序和物理設備溝通的橋樑,咱們在 Queue
上提交命令,而後再交由 GPU 去執行。
回到本小節的內容,建立 Device 組件,它的函數指針形式以下:
// 建立 Device 的函數指針
typedef VkResult (VKAPI_PTR *PFN_vkCreateDevice) (VkPhysicalDevice physicalDevice, // 物理設備 const VkDeviceCreateInfo* pCreateInfo, // 調用套路里面的 CreateInfo const VkAllocationCallbacks* pAllocator, VkDevice* pDevice); // 要建立的 Device 類
複製代碼
建立一個 Device
對象,不只須要指定具體的物理設備 VkPhysicalDevice
,另外還須要該物理設備上的 Queue
相關信息。
在 VkDeviceCreateInfo
結構體中須要一個參數是 VkDeviceQueueCreateInfo
,它的建立以下:
// 建立 Queue 所需的相關信息
VkDeviceQueueCreateInfo queue_info = {};
// 找到屬性爲 VK_QUEUE_GRAPHICS_BIT 的索引
bool found = false;
for (unsigned int i = 0; i < queue_family_count; ++i) {
if (queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_info.queueFamilyIndex = i;
found = true;
break;
}
}
float queue_priorities[1] = {0.0};
// 結構體的類型
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = nullptr;
queue_info.queueCount = 1;
// Queue 的優先級
queue_info.pQueuePriorities = queue_priorities;
複製代碼
接下來就能夠完成 Queue
的建立:
// 建立 Device 所需的相關信息類
VkDeviceCreateInfo device_info = {};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.pNext = nullptr;
// Device 所需的 Queue 相關信息
device_info.queueCreateInfoCount = 1; // Queue 個數
device_info.pQueueCreateInfos = &queue_info; // Queue 相關信息
// Layer 和 Extension 暫時爲空,不影響運行,後續再補上
device_info.enabledExtensionCount = 0;
device_info.ppEnabledExtensionNames = NULL;
device_info.enabledLayerCount = 0;
device_info.ppEnabledLayerNames = NULL;
device_info.pEnabledFeatures = NULL;
res = vkCreateDevice(gpus[0], &device_info, nullptr, &device);
複製代碼
完成了 Device
建立以後,Queue
的建立也簡單多了,直接調用以下函數就行了:
typedef void (VKAPI_PTR *PFN_vkGetDeviceQueue) (VkDevice device, // 建立的 Device 對象 uint32_t queueFamilyIndex, // queueFlags 爲 VK_QUEUE_GRAPHICS_BIT 的索引 uint32_t queueIndex, VkQueue* pQueue); // 要建立的 Queue
// 代碼示例
vkGetDeviceQueue(info.device, info.graphics_queue_family_index, 0, &info.queue);
複製代碼
完成了 Instance
、Device
、Queue
組件的建立以後,還有一件要作的事情就是釋放它們,銷燬組件。
按照先進後出的方式進行銷燬,Instance
最早建立反而最後銷燬,和 Device
相關聯的 Queue
當 Device
銷燬了,Queue
也隨之銷燬了。
// 銷燬 Device
vkDestroyDevice(info.device, nullptr);
// 銷燬 Instance
vkDestroyInstance(info.instance, nullptr);
複製代碼
這裏有一些不錯的參考地址和書籍:
也能夠參考個人項目實踐代碼:
以上是我的的學習經驗,僅供參考,有講的不對之處,歡迎指出,也能夠加我微信一塊兒交流學習: zh_ying_13
(備註博客).
有相關工做機會的求帶入坑~~~
敲一遍上述的代碼,會發現 Vulkan 在 API 調用上仍是有跡可循的,重點是要理解了每一個參數的含義,多結合官方的文檔來學習、實踐、
歡迎關注微信公衆號:【紙上淺談】,得到最新文章推送~~~