第 2 章 你的第一個(gè) Vulkan 偽代碼程序

在上一章中,我們提供了一個(gè)比較基本的介紹,以便可視化新一代的 Vulkan API。 我們通過這套 API 的高級(jí)生態(tài)系統(tǒng)設(shè)計(jì)進(jìn)行了盤點(diǎn),并了解內(nèi)部模塊的功能,以此來理解其執(zhí)行模式。

在本章中,我們會(huì)了解一下 Vulkan 環(huán)境的安裝過程,以便使用 Vulkan 偽代碼進(jìn)行編程做好準(zhǔn)備。 Vulkan 的明確性會(huì)使編程代碼變得更加冗長(zhǎng)。 在 Vulkan 中,一個(gè)簡(jiǎn)單的 Hello World !!! 程序最終可能會(huì)有大約 1,500 行代碼。 這意味著即使是一個(gè)簡(jiǎn)單的例子,對(duì)初學(xué)者來說也是一個(gè)挑戰(zhàn)。 但我們先不管這些具體的代碼;我們會(huì)將整個(gè) Hello World! 程序使用簡(jiǎn)單的偽代碼編程的方式進(jìn)行講解。

初學(xué)者還將學(xué)習(xí)如何以一種用戶友好的方式構(gòu)建他們的第一個(gè) Vulkan 應(yīng)用程序,即分步驟操作的方法。 在本書接下來的章節(jié)中,我們會(huì)深入研究實(shí)際的編碼過程,并利用 Vulkan 編程來解決問題。 所以學(xué)習(xí)過程分為幾個(gè)模塊、多個(gè)章節(jié)。

本章為其余的章節(jié)奠定了基礎(chǔ)。 在這里,我們將構(gòu)建一個(gè)非常簡(jiǎn)單的 Hello World!。這是一個(gè) 偽代碼程序,我們將通過這份代碼來了解使用 Vulkan 構(gòu)建簡(jiǎn)單三色三角形的過程。 本章將涵蓋以下主題:

  • 安裝 Vulkan 環(huán)境
  • HelloWorld !!! 偽代碼程序
  • 整合在一起
  • 安裝 Vulkan

我們已經(jīng)討論了很多 Vulkan 的內(nèi)容, 現(xiàn)在讓我們深入研究一下 Vulkan 的安裝過程,以及為了讓 Vulkan 進(jìn)行一些實(shí)際的作業(yè)任務(wù)需要掌握的內(nèi)容。

提示

在繼續(xù)安裝之前,請(qǐng)仔細(xì)閱讀本書提供的代碼文件中的軟硬件要求。 如果您的系統(tǒng)符合上述要求,那么您最好按照本章介紹的安裝過程一起操作。

請(qǐng)按照以下說明安裝 Vulkan:

  1. Vulkan 驅(qū)動(dòng)程序:大多數(shù)供應(yīng)商現(xiàn)在都將 Vulkan 支持包含在常規(guī)的驅(qū)動(dòng)程序包中。 首先,安裝 Vulkan 驅(qū)動(dòng)程序。 您可以選擇安裝位置;否則就會(huì)使用默認(rèn)位置進(jìn)行安裝。 例如,如果您正在安裝 NVIDIA 驅(qū)動(dòng)程序,安裝程序首先會(huì)檢查系統(tǒng)的配置以掃描安裝驅(qū)動(dòng)程序的任何兼容性問題。同時(shí)它會(huì)升級(jí)系統(tǒng)上預(yù)裝的驅(qū)動(dòng)程序。
  2. 安裝 Python:安裝 Python 并確保將其添加到環(huán)境變量 PATH 中。 這可以通過簡(jiǎn)單地 勾選Add Python <version> to PATH的復(fù)選框來實(shí)現(xiàn)。
  3. 安裝 CMake:接下來安裝 CMake。 確保選中 Add CMake to the system PATH for all users。 您可以使用默認(rèn)位置進(jìn)行安裝。
  4. 安裝 SDK:安裝 LunarG SDK。 使用默認(rèn)位置是沒有什么問題的。

注意

LunarG SDK 包含 Vulkan 規(guī)范,手冊(cè)以及有助于構(gòu)建項(xiàng)目的必要庫。 它還包含了可快速啟動(dòng)的演示示例,以檢查安裝狀態(tài)。 如果您能夠成功運(yùn)行示例的可執(zhí)行文件,這意味著 Vulkan 驅(qū)動(dòng)程序和 SDK 已正確安裝。 您可以在<Lunar-G SDK Path> / Bin 或<Lunar-G SDK Path> / Bin32(用于 32 位系統(tǒng))下找到這些示例。

Hello World!!! 偽代碼

在本節(jié)中,我們將構(gòu)建我們的第一個(gè) Hello World! Vulkan 應(yīng)用程序。 該應(yīng)用程序使用偽代碼編程模型構(gòu)建,它具有以下優(yōu)點(diǎn):

  • 通過按步驟進(jìn)行的過程學(xué)習(xí)如何構(gòu)建 Vulkan 應(yīng)用程序。
  • 由于 Vulkan 編碼冗長(zhǎng),因此初學(xué)者可能會(huì)迷失在細(xì)節(jié)中。 該偽代碼僅僅強(qiáng)調(diào)了易于理解的必要細(xì)節(jié)。
  • 這種緊湊形式的代碼程序,對(duì)于首次使用 Vulkan 的讀者來說更容易記憶。
  • 每個(gè)偽代碼都使用真正的 Vulkan API,并解釋其控制流程。
  • 在本章的最后,如果你是一個(gè)完完全全的初學(xué)者,你將能夠理解 Vulkan 編程以及從零開始構(gòu)建應(yīng)用程序的所有必要線索。 此外,您將了解 Vulkan API 的高級(jí)概念及其職責(zé)和功能。
  • 要詳細(xì)了解 API,請(qǐng)使用 LunarG SDK 提供的 Vulkan 規(guī)范。 或者參考 https://www.khronos.org/registry/vulkan/specs/1.0/apispec.html。

提示

鑒于本章的篇幅所限,不可能提供每個(gè)數(shù)據(jù)結(jié)構(gòu)字段和 API 參數(shù)的詳細(xì)描述。 偽代碼僅限于為大多數(shù)重要的數(shù)據(jù)結(jié)構(gòu)或 API 提供高級(jí)定義、概述和最多一到兩行的相關(guān)功能描述。 在我們繼續(xù)閱讀本書后續(xù)章節(jié)時(shí),會(huì)全面介紹 Vulkan 的所有 API 以及相關(guān)數(shù)據(jù)結(jié)構(gòu)。

初始化 - 與設(shè)備握手

Vulkan 初始化包括驗(yàn)證層(validation layer)屬性和實(shí)例對(duì)象(VkInstance)創(chuàng)建的初始化。 一旦創(chuàng)建實(shí)例,就要檢查現(xiàn)有系統(tǒng)上可用的物理設(shè)備(VkPhysicalDevice)。 選擇預(yù)期的物理設(shè)備,并借助實(shí)例對(duì)象創(chuàng)建相應(yīng)的邏輯設(shè)備(VkDevice)。 在 Vulkan 編程中,邏輯設(shè)備用于大多數(shù)代表物理設(shè)備的邏輯表示的 API 中。

2-001.png

Vulkan 通過錯(cuò)誤 (error) 和驗(yàn)證層 (validation layers) 提供調(diào)試功能。 有兩種類型的擴(kuò)展:

  • 特定于實(shí)例 Instance-specific:提供了全局級(jí)的擴(kuò)展
  • 特定于設(shè)備 Device-specific:提供了物理設(shè)備特定的擴(kuò)展
2-002.png

在開始時(shí),會(huì)列舉系統(tǒng)中的全局層 (global layers) 以及設(shè)備特定 (device-specific) 的擴(kuò)展;這些由 Vulkan 驅(qū)動(dòng)程序公開。 可以將全局層和擴(kuò)展注入到要在全局級(jí)別(global level)啟用的實(shí)例對(duì)象中。 但是,僅在設(shè)備級(jí)別啟用擴(kuò)展會(huì)使其僅在該特定設(shè)備上有效。

初始化負(fù)責(zé)創(chuàng)建實(shí)例 (instance ) 和設(shè)備對(duì)象 (device objects)。 另外,還會(huì)查詢?nèi)謱?/ 擴(kuò)展(global layers/extensions),并在全局級(jí)別或?qū)嵗?jí)別啟用它們。 同樣,擴(kuò)展在特定設(shè)備上啟用。 以下是偽代碼的初始化過程:

  1. 枚舉 Instance Layer 屬性:Vulkan 首先與加載程序通信并找到驅(qū)動(dòng)程序。 該驅(qū)動(dòng)程序公開了很多擴(kuò)展 extensions 和層 layers,這些擴(kuò)展和層可能隨著每個(gè)新驅(qū)動(dòng)的安裝或不同的 GPU 供應(yīng)商不同而有所差異。 vkEnumerateInstanceLayerProperties 檢索層的數(shù)量及其屬性。 每個(gè)層可能包含多個(gè)擴(kuò)展,它們都是可以使用 vkEnumerateInstanceExtensionProperties 進(jìn)行查詢的:
/*** 1. Enumerate Instance Layer properties ***/


// Get number of instance layers
uint32_t instanceLayerCount;

// Use second parameter as NULL to return the layer count
vkEnumerateInstanceLayerProperties(&instanceLayerCount, NULL);

VkLayerProperties *layerProperty = NULL; vkEnumerateInstanceLayerProperties(&instanceLayerCount,
layerProperty);

// Get the extensions for each available instance layer
foreach layerProperty{
VkExtensionProperties *instanceExtensions;
res = vkEnumerateInstanceExtensionProperties(layer_name, &instanceExtensionCount, instanceExtensions);
}
  1. 實(shí)例 Instance 的創(chuàng)建:實(shí)例對(duì)象(VkInstance)是使用 vkCreateInstance()API 創(chuàng)建的,參數(shù)指定了要啟用的、用于驗(yàn)證或調(diào)試目的的層名和擴(kuò)展名。 這些名稱在 VkInstanceCreateInfo 結(jié)構(gòu)中指定:
/*** 2. Instance Creation ***/


// Vulkan instance object
VkInstance instance;
VkInstanceCreateInfo instanceInfo   = {};

// Specify layer names that needs to be enabled on instance.
instanceInfo.ppEnabledLayerNames    = {
"VK_LAYER_LUNARG_standard_validation", "VK_LAYER_LUNARG_object_tracker" };

// Specify extensions that needs to be enabled on instance.
instanceInfo.ppEnabledExtensionNames = {
VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME};

// Create the Instance object
vkCreateInstance(&instanceInfo, NULL, &instance);
  1. 設(shè)備 Device 的創(chuàng)建:枚舉現(xiàn)有系統(tǒng)上的物理設(shè)備或 GPU 的數(shù)量 ------- 通過 vkEnumeratePhysicalDevices()API:
/*** 3. Enumerate physical devices ***/

VkPhysicalDevice    gpu;        // Physical device uint32_t gpuCount;       // Pysical device count vector<VkPhysicalDevice>gpuList;    // List of physical devices
// Get number of GPU count
vkEnumeratePhysicalDevices(instance, &gpuCount, NULL);

// Get GPU information
vkEnumeratePhysicalDevices(instance, &gpuCount, gpuList);

對(duì)于每個(gè)物理設(shè)備,我們會(huì)按照實(shí)例創(chuàng)建期間使用的、同樣方式枚舉特定于設(shè)備的擴(kuò)展。

注意

對(duì)于基于實(shí)例的枚舉,請(qǐng)使用 vkEnumerateInstanceLayerProperties 和 vkEnumerateInstanceExtensionProperties API。 但是,基于設(shè)備的層的枚舉已經(jīng)棄用了;因此,可以使用擴(kuò)展的 vkEnumerateDeviceExtensionProperties 進(jìn)行枚舉。

通過手中的物理設(shè)備列表,就可以查詢以下信息:

  • 隊(duì)列 Queue 和隊(duì)列類型 queue types:使用 vkGetPhysicalDeviceQueueFamilyProperties API 查詢可用物理設(shè)備的隊(duì)列和隊(duì)列屬性。 在查詢的隊(duì)列中,搜索具有圖形處理功能的隊(duì)列并將其隊(duì)列族索引 queue family index 存儲(chǔ)在應(yīng)用程序中供以后使用。 選擇圖形隊(duì)列 graphics queue 是因?yàn)槲覀冎粚?duì)繪圖操作感興趣。
  • 內(nèi)存 Memory 信息:vkGetPhysicalDeviceMemoryProperties()API 檢索目標(biāo)物理設(shè)備上可用的內(nèi)存類型。
  • 物理設(shè)備屬性 Physical device properties:或者,您可以存儲(chǔ)物理設(shè)備的屬性,以便在編程時(shí)檢索某些特定的信息。 這可以使用 vkGetPhysicalDeviceProperties()API 來完成。

設(shè)備對(duì)象 device object 是使用 vkCreateDevice()API 創(chuàng)建的。 這是應(yīng)用程序空間中物理設(shè)備的邏輯表示。 從現(xiàn)在開始,程序會(huì)在很多地方使用設(shè)備對(duì)象 device object :

/*** 4. Create Device ***/


// Get Queue and Queue Type
vkGetPhysicalDeviceQueueFamilyProperties(gpu,
&queueCount, queueProperties);

// Get the memory properties from the physical device or GPU
vkGetPhysicalDeviceMemoryProperties(gpu, &memoryProperties);

// Get the physical device or GPU properties
vkGetPhysicalDeviceProperties(gpu, &gpuProps);

// Create the logical device object from physical device VkDeviceCreateInfo deviceInfo = {}; vkCreateDevice(gpuList[0],&deviceInfo, NULL, &device);

下圖以備忘表格的形式總結(jié)了創(chuàng)建 Vulkan 實(shí)例和設(shè)備的方法;你可以參考,將其作為這些過程的快速回顧:

2-003.png

Swapchain 初始化 - 查詢 WSI 擴(kuò)展

展示層 presentation 負(fù)責(zé)在輸出窗口中顯示渲染內(nèi)容。 為此,我們需要一個(gè)空的窗口,這樣就可以把我們繪圖的圖像填充到其中。 使用 CreateWindowEx(Windows)或 xcb_create_window(Linux)API 創(chuàng)建一個(gè)空窗口。

展示層 presentation 首先需要使用基于實(shí)例 instance 和基于設(shè)備 device 的 WSI 擴(kuò)展 API 進(jìn)行初始化。 這些 API 允許您使用各種表面屬性 surface properties 創(chuàng)建展示表面 presentation surface。

注意

這些 API 必須動(dòng)態(tài)鏈接并在應(yīng)用程序中作為函數(shù)指針進(jìn)行存儲(chǔ)。 使用 vkGetInstanceProcAddr()API 查詢這些 API,如下表所示。

對(duì)于基于實(shí)例的擴(kuò)展 API,請(qǐng)參考以下內(nèi)容:

vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR
vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfacePresentModesKHR
vkDestroySurfaceKHR

同樣,對(duì)于基于設(shè)備的擴(kuò)展 API,請(qǐng)參考以下內(nèi)容:

vkCreateSwapchainKHR vkDestroySwapchainKHR vkGetSwapchainImagesKHR
vkAcquireNextImageKHR vkQueuePresentKHR

獲得這些 API 來完成所有與展示相關(guān) presentation-related 的功能是非常不錯(cuò)的。 讓我們看看還有什么是必需的:

  • 創(chuàng)建一個(gè)抽象表面對(duì)象 abstract surface object:表面創(chuàng)建的第一件事是創(chuàng)建 VkSurfaceKHR 對(duì)象。 該對(duì)象抽象了本機(jī)平臺(tái)(Windows,Linux,Wayland,Android 等)窗口 / 表面機(jī)制(windowing/surface mechanisms)。 該對(duì)象是使用 vkCreate <Win32 / Wayland / Android> SurfaceKHR()API 創(chuàng)建的。
  • 使用帶有展示能力的圖形隊(duì)列 Using a graphics queue with the presentation:使用創(chuàng)建的抽象表面對(duì)象 abstract surface object 并通過 vkGetPhysicalDeviceSurfaceSupportKHR()API 搜索能夠支持展示功能的圖形隊(duì)列 graphics queue。

注意

存儲(chǔ)此搜索過的隊(duì)列的句柄或索引。 稍后,它將用于查詢其表面屬性并創(chuàng)建此隊(duì)列的邏輯對(duì)象(下一步)。

  • 獲取兼容隊(duì)列 compatible queue:在開始記錄任何類型的命令緩沖區(qū) command buffer 之前,必須獲取隊(duì)列 queue,用來提交命令緩沖區(qū) command buffer。 使用 vkGetDeviceQueue()API 并指定我們?cè)谏弦徊街胁樵兊降募嫒蓐?duì)列的句柄或索引。
  • 查詢表面格式 surface formats:使用 vkGetPhysicalDeviceSurfaceFormatsKHR API 檢索物理設(shè)備支持的所有公開的表面格式:
/*** 5. Presentation Initialization ***/


// Create an empty Window CreateWindowEx(...);  /*Windows*/ xcb_create_window(...); /*Linux*/
// Query WSI extensions,store it as function pointers. For example:

// vkCreateSwapchainKHR, vkCreateSwapchainKHR .....


// Create an abstract surface object VkWin32SurfaceCreateInfoKHR createInfo = {}; vkCreateWin32SurfaceKHR(instance, &createInfo, NULL, &surface);

// Among all queues, select a queue that supports presentation
foreach Queue in All Queues{ vkGetPhysicalDeviceSurfaceSupportKHR
(gpu, queueIndex, surface, &isPresentationSupported);
// Store this queue's index
if (isPresentationSupported) { graphicsQueueFamilyIndex = Queue.index; break;
}
}

// Acquire compatible queue supporting presentation

// and is also a graphics queue
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &queue);


// Allocate memory for total surface format count uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR
(gpu, surface, &formatCount, NULL);

VkSurfaceFormatKHR *surfaceFormats = allocate memory
(formatCount * VkSurfaceFormatKHR);

// Grab the surface format into VkSurfaceFormatKHR objects
vkGetPhysicalDeviceSurfaceFormatsKHR
(gpu, surface, &formatCount, surfaceFormats);

下圖簡(jiǎn)要介紹了展示層 presentation 初始化的概況:

2-004.png

命令緩沖區(qū) Command buffer 初始化 - 分配命令緩沖區(qū) command buffers

在我們開始創(chuàng)建展示表面 presentation surface 之前,我們需要有命令緩沖區(qū) command buffers。 命令緩沖區(qū)記錄命令并將它們提交給兼容的隊(duì)列進(jìn)行處理。

命令緩沖區(qū)初始化包括以下內(nèi)容:

命令池 Command pool 的創(chuàng)建:請(qǐng)記住,我們保存了支持展示功能 presentation 的兼容圖形隊(duì)列 graphics queue 的句柄。 現(xiàn)在我們將通過 vkCreateCommandPool()API 并利用這個(gè)索引或句柄來創(chuàng)建一個(gè)與此隊(duì)列族兼容的命令池 command pool。

  • 分配命令緩沖區(qū) command buffer:命令緩沖區(qū)可以使用 vkAllocateCommandBuffers()API 從創(chuàng)建的命令池中進(jìn)行簡(jiǎn)單的分配。

注意

如果要重復(fù)使用的話,則不必為每個(gè)幀都從命令池 command pool 分配命令緩沖區(qū) command buffers。 如果不再需要現(xiàn)有的命令緩沖區(qū),則可以高效地重用它們。

命令緩沖池 command buffer pool 用于分配內(nèi)存區(qū)域以此來創(chuàng)建命令緩沖區(qū) command buffer,而不用引入全局同步:

2-005.png

資源對(duì)象 Resource objects - 管理圖像 images 和緩沖區(qū) buffers

了解 Vulkan 中資源類型的概念是非常重要的。 從現(xiàn)在開始,我們會(huì)經(jīng)常處理資源管理。 資源管理包括資源的創(chuàng)建、分配以及綁定。 例如,展示表面 presentation surface 本身就像處理其他普通的 Vulkan 資源類型一樣處理繪圖表面 drawing surface。

Vulkan 將資源分為兩種類型,緩沖區(qū) Buffer圖像 Image ,如下圖所示:

2-006.png

這些資源會(huì)進(jìn)一步劃分為視圖 view; 讓我們了解這些術(shù)語:

  • 緩沖區(qū) Buffer:緩沖區(qū)對(duì)象 buffer object 使用用線性的數(shù)組類型表示資源。 緩沖區(qū)對(duì)象是 VkBuffer 類型的,并且使用 vkCreateBuffer()API 進(jìn)行創(chuàng)建。 該 API 使用 VkBufferCreateInfo 結(jié)構(gòu)作為參數(shù)輸入,此參數(shù)指定了在創(chuàng)建對(duì)象期間可以使用的各種屬性。 例如,您可以指定圖像的平鋪、圖像的使用方式、尺寸大小、隊(duì)列兼容性等。 現(xiàn)在我們來看看是什么構(gòu)成了緩沖區(qū)視圖:
    • 緩沖區(qū)視圖 Buffer view:緩沖區(qū)視圖 buffer view(VkBufferView)表示數(shù)據(jù)緩沖區(qū) data buffer 本身。 它用于以連續(xù)的方式容納數(shù)據(jù),按照特定的數(shù)據(jù)解釋格式排列。 它可以在 vkCreateBufferView()API 的幫助下進(jìn)行創(chuàng)建。 接受一個(gè) VkBufferViewCreateInfo 結(jié)構(gòu)作為輸入?yún)?shù),其中可以指定各種緩沖區(qū)特定的屬性,例如它的緩沖區(qū)對(duì)象 buffer object(VkBuffer)、格式、緩沖區(qū)視圖的范圍等。
  • 圖像 Image:這是通過 VkImage 以編程方式來表示的。 該對(duì)象存儲(chǔ)一維到三維的緩沖區(qū)數(shù)組 buffer arrays。 此對(duì)象是使用 vkCreateImage()API 創(chuàng)建的。 與緩沖區(qū)對(duì)象 buffer object 類似,此 API 使用 VkImageCreateInfo 結(jié)構(gòu)在對(duì)象創(chuàng)建期間指定各種屬性。 現(xiàn)在讓我們看看圖像視圖是什么:
    • 圖像視圖 Image view:與緩沖區(qū)視圖 buffer view 類似,圖像視圖對(duì)象 image view object 的類型為 VkImageView。 使用 vkCreateImageView()API 和 VkImageViewCreateInfo 結(jié)構(gòu)來創(chuàng)建圖像視圖對(duì)象 image view object。

注意

應(yīng)用程序并不會(huì)直接使用緩沖區(qū)(VkBuffer)和圖像(VkImage)對(duì)象,相反,它依賴于它們各自的視圖:VkBufferView 和 VkImageView。

應(yīng)用程序并不會(huì)直接使用緩沖區(qū)(VkBuffer)和圖像(VkImage)對(duì)象,相反,它依賴于它們各自的視圖:VkBufferView 和 VkImageView。

讓我們快速回顧一下。 到目前為止,我們已經(jīng)創(chuàng)建了一個(gè) Vulkan 實(shí)例 instance、一個(gè)表示我們物理設(shè)備 physical device 的邏輯設(shè)備 logical device,并且我們已經(jīng)查詢了隊(duì)列屬性 queue properties 并存儲(chǔ)了支持展示功能 presentation 的隊(duì)列族索引 queue family index。 我們?yōu)?WSI 擴(kuò)展創(chuàng)建了函數(shù)指針并且理解了 Vulkan 資源類型。 我們還從命令池 command pool 初始化并創(chuàng)建了我們的命令緩沖區(qū) command buffers。

這其中涵蓋了啟動(dòng)我們命令緩沖區(qū)記錄 command buffer recording 過程所需的全部?jī)?nèi)容。

提示

命令緩沖區(qū)中應(yīng)該記錄些什么內(nèi)容呢?

a)為交換鏈和深度 / 模板測(cè)試構(gòu)建繪制圖像和深度圖。

b)創(chuàng)建著色器模塊,用于與著色器程序相關(guān)聯(lián)。

c)利用描述符集和管線布局將資源綁定到著色器。

d)創(chuàng)建并管理 Render Pass 和 framebuffer 對(duì)象。

e) 繪圖操作。

使用 vkBeginCommandBuffer()API 開始命令緩沖區(qū)的記錄。 它定義了命令緩沖區(qū)的起始范圍;此后,任何指定的命令都會(huì)被記錄在命令緩沖區(qū)中。

現(xiàn)在,我們來學(xué)習(xí)如何創(chuàng)建交換鏈。 在這一部分,我們將從交換鏈中獲取繪制圖像,以便進(jìn)行渲染:

  1. 獲取表面的功能:使用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR()API 查詢表面的功能,例如當(dāng)前尺寸、可能的最小 / 最大尺寸、可能的轉(zhuǎn)換功能等等。
  2. 獲取表面呈現(xiàn)模式 surface presentation modes:呈現(xiàn)模式 presentation mode 告訴我們?nèi)绾胃吕L圖表面 drawing surface,例如,它是以即時(shí)模式更新還是垂直空白依賴模式更新 vertical blank dependent 等等。 呈現(xiàn)模式 presentation modes 可以使用 vkGetPhysicalDeviceSurfacePresentModesKHR()API 檢索。
  3. 創(chuàng)建交換鏈 swapchain:使用表面的功能 surface capabilities 與呈現(xiàn)模式 presentation modes 一起創(chuàng)建交換鏈對(duì)象。 這些特性以及許多其他的參數(shù)(如尺寸大小、表面格式等)都在 VkSwapChainCreateInfo 結(jié)構(gòu)中指定,該結(jié)構(gòu)被傳遞給 vkCreateSwapchainKHR()API 用來創(chuàng)建交換鏈對(duì)象。
  4. 檢索交換鏈圖像:查詢交換鏈返回的圖像表面的數(shù)量,并使用 vkGetSwapchainImagesKHR()API 檢索相應(yīng)的圖像對(duì)象(VkImage)。 例如,如果交換鏈支持雙緩沖,那么它應(yīng)該返回兩個(gè)圖像,并且可以對(duì)這兩個(gè)圖像進(jìn)行繪圖。

注意

對(duì)于交換鏈圖像,應(yīng)用程序一端不需要進(jìn)行內(nèi)存分配。 在內(nèi)部,交換鏈已經(jīng)處理了內(nèi)存的分配并返回烘焙后的對(duì)象。 應(yīng)用程序只需要指定如何通過圖像視圖使用圖像就可以了。 圖像視圖描述了圖像的使用方式。

  1. 設(shè)置圖像布局:對(duì)于每個(gè)圖像,設(shè)置實(shí)現(xiàn)兼容的布局并添加管線屏障。 根據(jù) Vulkan 規(guī)范,管線屏障會(huì)在一組命令之間插入執(zhí)行依賴和一組內(nèi)存依賴;首先插入命令緩沖區(qū),然后是在命令緩沖區(qū)中插入若干個(gè)命令, 這可以使用 vkCmdPipelineBarrier()API 完成這項(xiàng)操作。 通過插入屏障,可以保證圖像視圖在應(yīng)用程序使用它之前可以在指定的布局中使用。
  2. 創(chuàng)建圖像視圖:由于應(yīng)用程序只能使用 VkImageView 對(duì)象,因此使用 vkCreateImageView()創(chuàng)建 VkImageView 對(duì)象。 保存視圖對(duì)象以供應(yīng)用程序后續(xù)使用:
/*** 6. Creating Swapchain ***/


//Start recording commands into command buffer
vkBeginCommandBuffer(cmd, &cmdBufInfo);

// Getting surface capabilities
vkGetPhysicalDeviceSurfaceCapabilitiesKHR
(gpu, surface, &surfCapabilities);

// Retrieve the surface presentation modes
vkGetPhysicalDeviceSurfacePresentModesKHR
(gpu, surface, &presentModeCount, NULL); VkPresentModeKHR presentModes[presentModeCount]; vkGetPhysicalDeviceSurfacePresentModesKHR
(gpu, surface, &presentModeCount, presentModes);

// Creating the Swapchain
VkSwapchainCreateInfoKHR swapChainInfo = {};
fpCreateSwapchainKHR(device, &swapChainInfo, NULL, &swapChain);

// Create the image view of the retrieved swapchain images
vkGetSwapchainImagesKHR
(device, swapChain, &swapchainImageCount, NULL); VkImage swapchainImages[swapchainImageCount];
vkGetSwapchainImagesKHR
(device, swapChain, &swapchainImageCount, swapchainImages);

// Retrieve the Swapchain images
foreach swapchainImages{

// Set the implementation compatible layout
SetImageLayout( . . .)

// Insert pipeline barrier
VkImageMemoryBarrier imgMemoryBarrier = { ... }; vkCmdPipelineBarrier(cmd,srcStages,destStages,0,0,
NULL,0,NULL,1,&imgMemoryBarrier);

// Insert pipeline barrier
vkCreateImageView(device, &colorImageView, NULL,
&scBuffer.view);

// Save the image view for application use
buffers.push_back(scBuffer);
}

下圖顯示了如何以圖像視圖對(duì)象(VkImageView)的形式使用 swapbuffer 圖像對(duì)象(VkImage):

2-007.png

創(chuàng)建深度圖

如果應(yīng)用程序打算使用深度測(cè)試,則需要深度圖。 對(duì)于 2D 繪圖邏輯來說,只要有交換鏈圖像就足夠了。 深度圖的創(chuàng)建過程與交換鏈圖像相同。 但是有一個(gè)區(qū)別:不同于交換鏈圖像,它們是現(xiàn)成的(由 vkGetPhysicalDeviceSurfaceFormatsKHR()返回),而深度圖對(duì)象(VkImage)由應(yīng)用程序手動(dòng)分配和創(chuàng)建的。

以下是深度圖的創(chuàng)建過程:

  1. 首先,使用 vkGetPhysicalDeviceFormatProperties()API 查詢物理設(shè)備的格式屬性
    ,深度圖會(huì)使用到這些屬性。
  2. 使用 vkCreateImage()API 創(chuàng)建一個(gè)圖像對(duì)象,并使用 vkGetImageMemoryRequirements()API 獲取所需資源的內(nèi)存需求。
  3. 接下來,使用檢索到的內(nèi)存需求屬性、通過 vkAllocateMemory()API 分配內(nèi)存。 通過調(diào)用 vkBindImageMemory()API 將分配的內(nèi)存綁定到創(chuàng)建的圖像對(duì)象。
  4. 與交換鏈中的繪圖圖像類似,設(shè)置適當(dāng)?shù)膱D像布局并根據(jù)應(yīng)用程序的要求創(chuàng)建圖像視圖。 有關(guān)設(shè)備內(nèi)存分配的更多詳細(xì)信息,請(qǐng)參閱下一節(jié)“資源分配 - 分配以及綁定設(shè)備內(nèi)存”。

參考下圖, 創(chuàng)建了一個(gè)新分配的深度圖(VkImage)并將其連接到它對(duì)應(yīng)的視圖類型(VKImageView),其對(duì)象駐留在內(nèi)存中:

2-008.png

以下偽代碼說明深度圖對(duì)象的創(chuàng)建過程,此深度圖會(huì)用于深度測(cè)試目的:

/*** 7. Creating Depth image ***/

// Query supported format features of the physical device
vkGetPhysicalDeviceFormatProperties(gpus,depthFormat,&properties);

// Create an image object
vkCreateImage(device, &imageInfo, NULL, &imageObject);

// Get the memory requirements for an image resource
vkGetImageMemoryRequirements(device, image, &memRequirements);

// Allocate memory
vkAllocateMemory(device, &memAlloc, NULL, &memorys);

// Bind memory
vkBindImageMemory(device, imageObject, mem, 0);

// Set the implementation compatible layout
SetImageLayout(. . .)

// Insert a pipeline barrier to ensure that specified image
// layout are created before it being used further
vkCmdPipelineBarrier(cmd, srcStages, destStages, 0, 0, NULL,
0, NULL, 1, &imgPipelineBarrier);

// Create an Image View
vkCreateImageView(device, &imgViewInfo, NULL, &view);

資源分配 - 分配以及綁定設(shè)備內(nèi)存

首次創(chuàng)建時(shí),Vulkan 資源(對(duì)于緩沖區(qū)來說就是 VkBuffer,對(duì)于圖像來說就是 VkImage)沒有任何關(guān)聯(lián)的后備內(nèi)存,即沒有分配實(shí)際的物理存儲(chǔ)空間。 因此在使用資源之前,我們需要為其分配內(nèi)存并將資源綁定到內(nèi)存。

為了分配 Vulkan 資源對(duì)象,首先應(yīng)用程序需要使用 vkGetPhysicalDeviceMemory-Properties()來查詢物理設(shè)備上的可用內(nèi)存。 此 API 公開了一個(gè)或多個(gè)堆,并進(jìn)一步暴露了這些堆中的一種或多種內(nèi)存類型。 這些暴露的屬性存儲(chǔ)在內(nèi)存控制結(jié)構(gòu)(VkPhysicalDeviceMemoryProperties)中。 對(duì)于一個(gè)典型的 PC 用戶來說,它會(huì)暴露兩個(gè)堆:系統(tǒng) RAM 和 GPU RAM。 此外,這些堆中的每一個(gè)都會(huì)根據(jù)其內(nèi)存類型進(jìn)行分類。

注意

特定于內(nèi)存屬性的查詢,比如堆類型,可以在應(yīng)用程序初始化期間進(jìn)行,并在應(yīng)用程序級(jí)對(duì)其進(jìn)行緩存以供后續(xù)使用。

現(xiàn)在,這些內(nèi)存類型中的每一種都可能擁有各種各樣的需要從物理設(shè)備查詢的屬性。 例如,某些內(nèi)存類型可能是 CPU 可見或不可見的;它們可以在 CPU 和 GPU 訪問之間保持一致性,緩存的或未緩存的,等等。 這樣的查詢?cè)试S應(yīng)用程序選擇適合其需求的正確內(nèi)存類型,以下是 Vulkan 中一般應(yīng)用程序用于資源分配的典型過程:

  • 內(nèi)存要求:資源對(duì)象(VkBuffer 和 VkImage)是根據(jù)其對(duì)象屬性(例如平鋪模式、使用標(biāo)志等)來創(chuàng)建的。 現(xiàn)在,每個(gè)對(duì)象都可能有不同的內(nèi)存需求,這就需要通過調(diào)用 vkGetBufferMemoryRequirements()或 vkGetImageMemoryRequirements()來查詢。 這有助于計(jì)算分配空間的大小;例如,返回的大小要處理填充對(duì)齊等。 它會(huì)考慮與該資源兼容的特定內(nèi)存類型的位掩碼。
  • 分配:內(nèi)存使用 vkAllocateMemory()API 來分配。 它接受設(shè)備對(duì)象(VkDevice)以及內(nèi)存控制結(jié)構(gòu)(VkPhysicalDeviceMemoryProperties)作為輸入?yún)?shù)。
  • 綁定:我們已經(jīng)得到了能夠幫助我們獲得正確內(nèi)存類型的內(nèi)存需求,使用這個(gè)信息,我們就可以分配內(nèi)存了。 現(xiàn)在我們可以使用 vkBindBufferMemory()或 vkBindImageMemory()API 將資源對(duì)象綁定到分配的內(nèi)存。
  • 內(nèi)存映射:內(nèi)存映射簡(jiǎn)而言之,就是如何更新物理設(shè)備內(nèi)存的內(nèi)容。 首先,使用 vkMapMemory()將設(shè)備內(nèi)存映射到主機(jī)內(nèi)存。 更新此映射后的內(nèi)存區(qū)域上的內(nèi)容(在主機(jī)端)并調(diào)用 vkUnmapMemory()API。 該 API 使用更新后的映射內(nèi)存的內(nèi)容更新設(shè)備內(nèi)存的內(nèi)容。

提供著色器 - 將著色器編譯為 SPIR-V 格式

使用 glslangValidator.exe(LunarG SDK 中的工具)編譯著色器文件,將它們從可讀的文本格式轉(zhuǎn)換為 SPIR-V 格式,這種格式是 Vulkan 可以理解的二進(jìn)制中間格式:

// VERTEX SHADER
#version 450

layout (location = 0) in vec4 pos; layout (location = 1) in vec4 inColor; layout (location = 0) out vec4 outColor;

out gl_PerVertex { vec4 gl_Position;
};

void main() {
outColor    = inColor; gl_Position      = pos; gl_Position.y = -gl_Position.y;
gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0;
}

// FRAGMENT SHADER
#version 450

layout (location = 0) in vec4 color; layout (location = 0) out vec4 outColor;

void main() { outColor = color;
};

以下的偽代碼顯示了在應(yīng)用程序中創(chuàng)建著色器模塊的過程。 通過調(diào)用 vkCreateShaderModule()API 創(chuàng)建給定著色器(頂點(diǎn),片段,幾何型,表面細(xì)分等)的著色器模塊。 要想使用該 API,需要提供 SPIR-V 格式的中間二進(jìn)制著色器代碼, 這是在 VkShaderModuleCreateInfo 控制結(jié)構(gòu)中進(jìn)行指定的:

/*** 8. Building shader module ***/

VkPipelineShaderStageCreateInfo vtxShdrStages   = { };
VkShaderModuleCreateInfo moduleCreateInfo   = { };

// spvVertexShaderData contains binary form of vertex shader
moduleCreateInfo.pCode = spvVertexShaderData;

// Create Shader module on the device
vkCreateShaderModule
(device, &moduleCreateInfo, NULL, &vtxShdrStages.module);

綁定布局 - 描述符和管線布局

描述符通過布局綁定槽將資源與著色器連接起來。 它通常用于將 uniform 和 sampler 資源類型與著色器連接。

多個(gè)描述符布局綁定可以存在于單個(gè)描述符集中;它們會(huì)以塊或數(shù)組的形式出現(xiàn),如下面的偽代碼所示。 然后將這些塊綁定到單個(gè)控制結(jié)構(gòu) VkDescriptorSetLayoutCreateInfo 中,并利用這個(gè)結(jié)構(gòu),通過調(diào)用 vkCreateDescriptorSetLayout()API 創(chuàng)建描述符布局對(duì)象。 描述符集布局表示描述符集包含的信息的類型。

雖然創(chuàng)建了描述符布局,但是目前還不能被底層管線所訪問。 為了提供訪問權(quán)限,我們需要?jiǎng)?chuàng)建一個(gè)管線布局。 管線布局是管線能夠訪問描述符集信息的手段。 它是通過調(diào)用 vkCreatePipelineLayout()API 創(chuàng)建的,該 API 使用到了 VkPipelineLayoutCreateInfo 控制結(jié)構(gòu)對(duì)象,其中包含了上述的描述符布局:

/*** 9. Creating descriptor layout and pipeline layout ***/


// Descriptor layout specifies info type associated with shaders
VkDescriptorSetLayoutBinding layoutBind[2];

layoutBind[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; layoutBind[0].binding = 0;
layoutBind[0].stageFlags    = VK_SHADER_STAGE_VERTEX_BIT;

layoutBind[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBind[1].binding   = 0;
layoutBind[1].stageFlags    = VK_SHADER_STAGE_FRAGMENT_BIT;

// Use layout bindings and create a descriptor set layout VkDescriptorSetLayoutCreateInfo descriptorLayout = {}; descriptorLayout.pBindings = layoutBind;

VkDescriptorSetLayout descLayout[2]; vkCreateDescriptorSetLayout
(device, &descriptorLayout, NULL, descLayout.data());

// Now use the descriptor layout to create a pipeline layout VkPipelineLayoutCreateInfo pipelineLayoutCI = { ... }; pipelineLayoutCI.pSetLayouts = descLayout.data(); vkCreatePipelineLayout
(device, &pipelineLayoutCI, NULL, &pipelineLayout);

注意

本章中的示例僅使用屬性(頂點(diǎn)位置和顏色)。 它不使用任何 uniform 或采樣器 sampler。 因此,在本章的這一點(diǎn)上,我們不需要定義描述符。 我們會(huì)在后面詳細(xì)了解更多關(guān)于描述符集的內(nèi)容,特別是第 10 章描述符和 push 常量。

創(chuàng)建渲染通道 - 定義通道屬性

接下來,創(chuàng)建一個(gè) Render Pass 對(duì)象。 渲染通道包含若干個(gè)子通道 subpass 和附件 attachment。 它向驅(qū)動(dòng)程序描述了繪圖工作的結(jié)構(gòu),數(shù)據(jù)如何在各個(gè)附件之間流動(dòng)或順序要求是怎樣的;以及運(yùn)行時(shí)行為,比如每次加載時(shí)如何處理這些附件,或者是否需要清除或保存信息。 Render Pass 對(duì)象是通過調(diào)用 vkCreateRenderPass()API 創(chuàng)建的。 它接受 subpass 和附件控制結(jié)構(gòu)作為參數(shù)。 有關(guān)更多信息,請(qǐng)參閱以下偽代碼:

/*** 10. Render Pass ***/


// Define two attachment for color and depth buffer VkAttachmentDescription attachments[2]; attachments[0].format = colorImageformat; attachments[0].loadOp = clear ? VK_ATTACHMENT_LOAD_OP_CLEAR
: VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[1].format = depthImageformat; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;

VkAttachmentReference colorReference, depthReference = {...};

// Describe the subpass, use color image and depth image VkSubpassDescription subpass       = {}; subpass.pColorAttachments = &colorReference; subpass.pDepthStencilAttachment  = &depthReference;

// Define RenderPass control structure
VkRenderPassCreateInfo rpInfo   = { &attachments,&subpass ...};

VkRenderPass renderPass; // Create Render Pass object vkCreateRenderPass(device, &rpInfo, NULL, &renderPass);

幀緩沖區(qū) - 將繪制圖像連接到渲染通道

幀緩沖區(qū)是圖像視圖的集合,對(duì)應(yīng)于 Render Pass 中指定的附件。 圖像視圖表示繪圖圖像 drawing image 或深度圖。 Render Pass 對(duì)象用于控制這些附件,通過在創(chuàng)建 Render Pass 對(duì)象時(shí)指定的屬性。

VkFramebufferCreateInfo 控制結(jié)構(gòu)接受 Render Pass 對(duì)象和附件以及其中的其他一些重要參數(shù),例如尺寸、附件數(shù)量、層等等。 該結(jié)構(gòu)被傳遞給 VkCreateFramebuffer()API 來創(chuàng)建幀緩沖區(qū)對(duì)象。

注意

用于表示顏色和深度緩沖區(qū)的附件必須是圖像視圖(VKImageView),而不是圖像對(duì)象(VkImage)。

下圖顯示了創(chuàng)建的幀緩沖區(qū)對(duì)象。 它包含用于繪制的、顏色緩沖區(qū)圖像的圖像視圖以及用于深度測(cè)試的深度視圖:

2-009.png

我們來看看創(chuàng)建幀緩沖區(qū)的過程,使用如下的偽代碼來示范:

/*** 11. Creating Frame buffers ***/

VkImageView attachments[2]; // [0] for color, [1] for depth
attachments[1] = Depth.view; VkFramebufferCreateInfo fbInfo = {};
fbInfo.renderPass   = renderPass;       // Pass render buffer object fbInfo.pAttachments        = attachments;  // Image view attachments fbInfo.width          = width;            // Frame buffer width fbInfo.height         = height;           // Frame buffer height
// Allocate memory for frame buffer objects, for each image

// in the swapchain, there is one frame buffer
VkFramebuffer framebuffers[number of draw imagein swap chain];

foreach (drawing buffer in swapchain) { attachments[0] = currentSwapChainDrawImage.view;
vkCreateFramebuffer(device, &fbInfo, NULL, &framebuffers[i]);
}

填充幾何圖形 - 將頂點(diǎn)存儲(chǔ)到 GPU 內(nèi)存中

接下來,定義會(huì)出現(xiàn)在顯示輸出上的幾何形狀。 在本章中,我們使用了一個(gè)簡(jiǎn)單的三色三角形。

以下屏幕截圖顯示與此三角形關(guān)聯(lián)的、交錯(cuò)存放的幾何圖形數(shù)據(jù)。 它包含了每個(gè)頂點(diǎn)的頂點(diǎn)位置,后面是顏色信息。 此數(shù)據(jù)數(shù)組需要通過 Vulkan 緩沖區(qū)對(duì)象(VkBuffer)提供給物理設(shè)備。

2-010.png

以下偽代碼演示了緩沖區(qū)對(duì)象的分配,映射和綁定過程:

/***    12. Populate Geometry - storing vertex into GPU memory ***/

static const VertexWithColor triangleData[] ={
/*{ x,  y,  z,  w,  r,  g,  b,  a },*/
{   0.0f,   1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0 },
{ -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0  },
{   1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0  },
};

VkBuffer        buffer; VkMemoryRequirements mem_requirement; VkDeviceMemory    deviceMemory;

// Create buffer object, query required memory and allocate VkBufferCreateInfo buffer_info = { ... }; vkCreateBuffer(device, &buffer_info, NULL, &buffer);

vkGetBufferMemoryRequirements(device, buffer, &mem_requirement);
VkMemoryAllocateInfo alloc_info = { ... }; vkAllocateMemory(device, &alloc_info, NULL, &(deviceMemory));
// Copy the triangleData to GPU using mapping and unmapping.
uint8_t *pData;
vkMapMemory(device, deviceMemory, 0, mem_requirement.size, 0, &pData);
memcpy(pData, triangleData, dataSize); /**** Copying data ****/ vkUnmapMemory(device, deviceMemory);

// Bind the allocated memory
vkBindBufferMemory(device, buffer, deviceMemory, 0);

創(chuàng)建緩沖區(qū)資源的過程與圖像對(duì)象的創(chuàng)建過程非常相似。 在這里,Vulkan 提供了用于分配,映射和綁定的、基于緩沖區(qū)的 API。 這與圖像對(duì)象管理 API 非常相似。 下表顯示了緩沖區(qū)和圖像資源管理 API 及相關(guān)的數(shù)據(jù)結(jié)構(gòu):

Buffer object Image object
VkBuffer VkImageView
VkBufferCreateInfo VkImageCreateInfo
vkCreateBuffer vkCreateImage
vkGetBufferMemoryRequirements vkGetImageMemoryRequirements
vkBindBufferMemory vkBindImageMemory
vkCreateBufferView vkCreateImageView

緩沖區(qū)最初并不與任何類型的內(nèi)存相關(guān)聯(lián)。 應(yīng)用程序必須先分配適當(dāng)?shù)脑O(shè)備內(nèi)存并將其綁定到緩沖區(qū),然后才能使用。 這一點(diǎn)與圖像不同,必須使用圖像視圖強(qiáng)制創(chuàng)建圖像才能夠在應(yīng)用程序中使用它們,而緩沖區(qū)對(duì)象則可以直接使用(如頂點(diǎn)屬性,Uniform 等)。 如果需要在著色器階段訪問緩沖區(qū)對(duì)象,則必須以緩沖區(qū)視圖對(duì)象的形式訪問緩沖區(qū)對(duì)象。

2-011.png

一旦頂點(diǎn)數(shù)據(jù)上傳到設(shè)備內(nèi)存中,就必須通知管線,并對(duì)這些數(shù)據(jù)進(jìn)行說明。 這將有助于檢索和解釋數(shù)據(jù)。 例如,前面的幾何圖形頂點(diǎn)數(shù)據(jù)包括以交錯(cuò)方式存儲(chǔ)的位置和顏色信息,并且每個(gè)屬性是 16 字節(jié)寬。 這些信息需要通過頂點(diǎn)輸入綁定(VkVertexInputBindingDescription)和頂點(diǎn)輸入屬性描述符(VkVertexInputAttributeDescription)控制結(jié)構(gòu)來傳遞給底層的管線。

  • VkVertexInputBindingDescription 包含了幫助管線讀取緩沖區(qū)資源數(shù)據(jù)的一些屬性,例如,考慮到要讀取的信息的速率(無論是基于頂點(diǎn)還是基于多個(gè)實(shí)例),每個(gè)信息單元之間的跨度。
  • VkVertexInputAttributeDescription 解釋緩沖區(qū)資源數(shù)據(jù)。

在下面的偽代碼中,位置和顏色屬性在頂點(diǎn)著色器中的第 0 和第 1 位置處進(jìn)行讀取。 由于數(shù)據(jù)是交錯(cuò)的形式,因此偏移量分別為 0 和 16:

/***    13. Vertex binding ***/

VkVertexInputBindingDescription viBinding; viBinding.binding    = 0;
viBinding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; viBinding.stride = sizeof(triangleData) /*data Stride*/;

VkVertexInputAttributeDescriptionviAttribs[2]; viAttribs[0].binding = 0;
viAttribs[0].location   = 0;
viAttribs[0].format = VK_FORMAT_R32G32B32A32_SFLOAT; viAttribs[0].offset    = 0;
viAttribs[1].binding    = 0;
viAttribs[1].location   = 1;
viAttribs[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; viAttribs[1].offset    = 16;

注意

控制結(jié)構(gòu)對(duì)象 viAttribs 和 viBinding 會(huì)在管線創(chuàng)建時(shí)使用。 管線對(duì)象包含多個(gè)狀態(tài),其中頂點(diǎn)輸入狀態(tài)會(huì)消費(fèi)有助于讀取和解釋緩沖區(qū)資源的對(duì)象。

管線狀態(tài)管理 - 創(chuàng)建管線

管線是多個(gè)狀態(tài)的集合。 每個(gè)狀態(tài)都包含一組屬性,它們定義了該狀態(tài)的執(zhí)行協(xié)議。 所有的這些狀態(tài)共同生產(chǎn)了一條管線。

有兩種類型的管線:

  • 圖形管線:該管線可以包含多個(gè)著色階段,包括頂點(diǎn)著色、片段著色、鑲嵌著色、幾何圖形著色等。 它有一個(gè)管線布局和多個(gè)固定功能的管線階段。
  • 計(jì)算管線:用于計(jì)算操作。 它由單個(gè)靜態(tài)的計(jì)算著色階段和管線布局組成。

管線狀態(tài)管理可以分為兩個(gè)步驟。 第一步就是定義各種狀態(tài)對(duì)象,其中包含一些重要的狀態(tài)控制屬性。 第二步,使用這些狀態(tài)對(duì)象創(chuàng)建一個(gè)管線對(duì)象。

定義狀態(tài)

管線可能會(huì)消耗多個(gè)狀態(tài),這些狀態(tài)的定義如下所示:

  • 動(dòng)態(tài)狀態(tài):動(dòng)態(tài)狀態(tài)用來通知管線在運(yùn)行時(shí)期望更改的有關(guān)狀態(tài)。 這允許管線授權(quán)特定的例程更新相應(yīng)的狀態(tài),而不是使用初始值。 例如,視口和剪切是動(dòng)態(tài)狀態(tài)。 VkPipelineDynamicStateCreateInfo 結(jié)構(gòu)指定應(yīng)用程序中的所有動(dòng)態(tài)狀態(tài)及其屬性。
  • 頂點(diǎn)輸入狀態(tài):該狀態(tài)有助于管線了解數(shù)據(jù)的讀取和解析。 使用 VkPipelineVertexInputStateCreateInfo 對(duì)象并指定頂點(diǎn)輸入綁定對(duì)象(VkVertexInputBindingDescription)和頂點(diǎn)輸入屬性描述符(VkVertexInputAttributeDescription)。
  • 光柵化狀態(tài):這是將圖元轉(zhuǎn)換為包含重要信息(例如顏色,深度和其他屬性)的、二維圖像的過程。 它由 VkPipelineRasterizationStateCreateInfo 結(jié)構(gòu)表示;這個(gè)結(jié)構(gòu)可以用剔除模式、正面方向、圖元類型、線寬等指定。
  • 顏色混合附件狀態(tài):混合是源顏色和目標(biāo)顏色的組合;這可以使用不同的屬性和混合方程按照各種方式進(jìn)行組合。 這是使用 VkPipelineColor-BlendStateCreateInfo 結(jié)構(gòu)表示的。
  • 視口狀態(tài):此狀態(tài)有助于控制視口轉(zhuǎn)換。 視口屬性可以使用 VkPipelineViewportState-CreateInfo 來指定。 可能有各種各樣的視口。 此狀態(tài)有助于確定所選視口的重要屬性,例如尺寸、起點(diǎn)、深度范圍等。 對(duì)于每個(gè)視口,都有一個(gè)相應(yīng)的裁剪矩形,用于定義裁剪測(cè)試的矩形邊界。
  • 深度模板狀態(tài):VkPipelineDepthStencilStateCreateInfo 控制結(jié)構(gòu)用于控制深度范圍測(cè)試、模板測(cè)試和深度測(cè)試。
  • 多重采樣狀態(tài):多采樣狀態(tài)包含了一些控制光柵化 Vulkan 圖元(例如點(diǎn),線和多邊形)抗鋸齒行為的重要屬性。 VkPipelineMultisampleStateCreateInfo 控制結(jié)構(gòu)可以用來指定這樣的控制屬性。
  • 以下偽代碼定義了各種管線狀態(tài)對(duì)象,用于創(chuàng)建圖形管線:
/*** 14. Defining states ***/


// Vertex Input state
VkPipelineVertexInputStateCreateInfo vertexInputStateInfo= {...}; vertexInputStateInfo.vertexBindingDescriptionCount = 1; vertexInputStateInfo.pVertexBindingDescriptions       = &viBinding; vertexInputStateInfo.vertexAttributeDescriptionCount  = 2; vertexInputStateInfo.pVertexAttributeDescriptions  = viAttribs;

// Dynamic states
VkPipelineDynamicStateCreateInfo dynamicState   = { ... };

// Input assembly state control structure
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo= { ... };

// Rasterization state control structure
VkPipelineRasterizationStateCreateInfo rasterStateInfo  = { ... };

// Color blend Attachment state control structure
VkPipelineColorBlendAttachmentState colorBlendSI    = { ... };

// Color blend state control structure
VkPipelineColorBlendStateCreateInfo colorBlendStateInfo = { ... };

// View port state control structure
VkPipelineViewportStateCreateInfo viewportStateInfo = { ... };

// Depth stencil state control structure
VkPipelineDepthStencilStateCreateInfo depthStencilStateInfo={..};

// Multisampling state control structure
VkPipelineMultisampleStateCreateInfo multiSampleStateInfo = {..};

創(chuàng)建圖形管線

管線狀態(tài)對(duì)象被打包到 VkGraphicsPipelineCreateInfo 控制結(jié)構(gòu)中。 該結(jié)構(gòu)提供了訪問圖形管線對(duì)象內(nèi)部管線狀態(tài)信息的手段。

管線狀態(tài)對(duì)象的創(chuàng)建可能是一項(xiàng)昂貴的操作。 這是性能關(guān)鍵點(diǎn)之一。 因此,管線狀態(tài)對(duì)象是從管線緩存(VkPipelineCache)創(chuàng)建的,以提供最大的性能。 這允許驅(qū)動(dòng)程序使用現(xiàn)有的基礎(chǔ)管線創(chuàng)建新的管線。

圖形管線對(duì)象是使用 vkCreateGraphicsPipelines()API 創(chuàng)建的。 此 API 接受管線緩存對(duì)象,用于從中分配 VkPipeline 對(duì)象,并接受 VkGraphicsPipelineCreateInfo 對(duì)象指定與此管線連接的所有狀態(tài):

/***    15. Creating Graphics Pipeline ***/

// Create the pipeline objects VkPipelineCache pipelineCache; VkPipelineCacheCreateInfo pipelineCacheInfo;
vkCreatePipelineCache(device, &pipelineCacheInfo, NULL,
&pipelineCache);

// Define the control structure of graphics pipeline VkGraphicsPipelineCreateInfo pipelineInfo; pipelineInfo.layout         = pipelineLayout; pipelineInfo.pVertexInputState    = &vertexInputStateInfo; pipelineInfo.pInputAssemblyState = &inputAssemblyInfo; pipelineInfo.pRasterizationState = &rasterStateInfo; pipelineInfo.pColorBlendState  = &colorBlendStateInfo; pipelineInfo.pMultisampleState  = &multiSampleStateInfo; pipelineInfo.pDynamicState     = &dynamicState; pipelineInfo.pViewportState        = &viewportStateInfo; pipelineInfo.pDepthStencilState   = &depthStencilStateInfo; pipelineInfo.pStages          = shaderStages;
pipelineInfo.stageCount = 2;
pipelineInfo.renderPass = renderPass;

// Create graphics pipeline
vkCreateGraphicsPipelines
(device, pipelineCache, 1, &pipelineInfo, NULL, &pipeline);

執(zhí)行渲染通道 - 繪制 Hello World?。?!

我們就快到了! 在這個(gè)階段,我們將在 Render Pass 階段的幫助下在繪圖表面渲染我們的簡(jiǎn)單三角形。 Render Pass 階段的執(zhí)行需要一個(gè)繪圖表面和一組命令集的一個(gè)記錄,該記錄定義一個(gè) Render Pass 的運(yùn)行行為。

獲取繪圖表面

在開始渲染任何東西之前,我們需要的第一個(gè)東西就是繪圖幀緩沖區(qū)。 我們已經(jīng)創(chuàng)建了幀緩沖區(qū)對(duì)象并把交換鏈圖形圖像與其(它包含交換鏈圖像視圖)關(guān)聯(lián)了起來。 現(xiàn)在,我們將使用 vkAcquireNextImageKHR()API 來確定繪圖操作當(dāng)前可用的繪圖圖像的索引。 使用獲取到的索引,我們就可以引用相應(yīng)的幀緩沖區(qū)并將其提供給 Render Pass 階段用于渲染目的:

/***    16. Acquiring drawing image ***/


// Define semaphore for synchronizing the acquire of draw image.

// Only acquire draw image when drawing is completed VkSemaphore imageAcquiredSemaphore; VkSemaphoreCreateInfo imageAcquiredSemaphoreCI = {...};
imageAcquiredSemaphoreCI.sType=VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; vkCreateSemaphore(device, &imageAcquiredSemaphoreCI, NULL,
&imageAcquiredSemaphore);

// Get the index of the next available swapchain image:
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAcquiredSemaphore, NULL, &swapChainObjCurrentBuffer);

當(dāng)使用兩個(gè)或多個(gè)交換鏈繪制圖像時(shí),需要使用同步機(jī)制。 只有在繪制的圖像已經(jīng)在顯示輸出上呈現(xiàn),并準(zhǔn)備好接受下一項(xiàng)作業(yè)時(shí)才能獲取繪圖圖像, 此狀態(tài)由 vkAcquireNextImageKHR()指示。 信號(hào)量對(duì)象可用于同步繪圖圖像的獲取。 信號(hào)量(VkSemaphore)可以使用 vkCreateSemaphore()API 創(chuàng)建;這個(gè)對(duì)象將會(huì)在命令緩沖區(qū)提交中使用。

準(zhǔn)備 Render Pass 控制結(jié)構(gòu)

渲染通道需要一些特定的信息,例如幀緩沖區(qū)、渲染通道對(duì)象、渲染區(qū)域尺寸、清除顏色、深度模板值等。 這些信息使用 VkRenderPassBeginInfo 控制結(jié)構(gòu)來指定。 此結(jié)構(gòu)稍后用于定義渲染通道的執(zhí)行。 以下偽代碼會(huì)幫助讀者詳細(xì)了解此結(jié)構(gòu)的用法:

/***    17. Preparing render pass control structure ***/
// Define clear color value and depth stencil values
const VkClearValue clearValues[2] = {
[0] = { .color.float32 = { 0.2f, 0.2f, 0.2f, 0.2f } },
[1] = { .depthStencil = { 1.0f, 0 } },
};

// Render pass execution data structure for a frame buffer
VkRenderPassBeginInfo beginPass;
beginPass.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; beginPass.pNext= NULL;
beginPass.renderPass    = renderPass;
beginPass.framebuffer =framebuffers[currentSwapchainImageIndex]; beginPass.renderArea.offset.x  = 0;
beginPass.renderArea.offset.y   = 0; beginPass.renderArea.extent.width  = width; beginPass.renderArea.extent.height = height; beginPass.clearValueCount     = 2;
beginPass.pClearValues  = clearValues;

渲染通道的執(zhí)行

Render Pass 的執(zhí)行在用戶指定的范圍內(nèi)進(jìn)行定義。 此范圍分別使用由 vkCmdBeginRenderPass()和 vkCmdEndRenderPass()API 定義的開始標(biāo)記和結(jié)束標(biāo)記來解釋。 在該范圍內(nèi),指定了以下命令,并自動(dòng)鏈接到當(dāng)前的 Render Pass:

  1. 綁定管線:使用 vkCmdBindPipeline()綁定圖形管線。
  2. 綁定幾何圖形緩沖區(qū):使用 vkCmdBindVertexBuffers()API 將頂點(diǎn)數(shù)據(jù)緩沖區(qū)對(duì)象(類型為 VkBuffer)提供給 Render Pass。
  3. 視口和裁剪:通過調(diào)用 vkCmdSetViewport()和 vkCmdSetScissor()API 指定視口以及裁剪尺寸。
    4.Draw 對(duì)象:指定繪圖命令,其中包含諸如需要從起始索引讀取的頂點(diǎn)數(shù)量,實(shí)例數(shù)量等信息。

在完成命令緩沖區(qū)記錄之前,要設(shè)置與實(shí)現(xiàn)兼容的圖像布局并通過調(diào)用 vkEndCommandBuffer()結(jié)束命令緩沖區(qū)的記錄:

/**** START RENDER PASS ****/
vkCmdBeginRenderPass(cmd, &beginPass, VK_SUBPASS_CONTENTS_INLINE);

// Bind the pipeline
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); const VkDeviceSize offsets[1] = { 0 };

// Bind the triangle buffer data
vkCmdBindVertexBuffers(cmd, 0, 1, &buffer, offsets);
// viewport = {0, 0, 500, 500, 0 ,1}
vkCmdSetViewport(cmd, 0, NUM_VIEWPORTS, &viewport);

// scissor  = {0, 0, 500, 500}
vkCmdSetScissor(cmd, 0, NUM_SCISSORS, &scissor);

// Draw command - 3 vertices, 1 instance, 0th first index
vkCmdDraw(cmd, 3, 1, 0, 0);

/**** END RENDER PASS ****/
vkCmdEndRenderPass(cmd);

// Set the swapchain image layout
setImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL . .);

/**** COMMAND BUFFER RECORDING ENDS HERE ****/
vkEndCommandBuffer(cmd);

下圖顯示了渲染通道的執(zhí)行過程。 它突出顯示了 Render Pass 范圍內(nèi)執(zhí)行的操作。

2-012.png

隊(duì)列提交和同步 - 發(fā)送作業(yè)

最后,我們使用若干命令(其中包括 Render Pass 信息和圖形管線)成功記錄了命令緩沖區(qū)。 命令緩沖區(qū)會(huì)被提交到隊(duì)列中進(jìn)行處理。 驅(qū)動(dòng)程序會(huì)讀取命令緩沖區(qū)并對(duì)其進(jìn)行調(diào)度安排。

注意

通常會(huì)把命令緩沖區(qū)打包成批處理,以便進(jìn)行高效渲染;因此,如果存在多個(gè)命令緩沖區(qū),則需要將它們打包到一個(gè) VkCommandBuffer 數(shù)組中。

在提交命令緩沖區(qū)之前,了解以前提交的批處理的狀態(tài)很重要。 如果處理成功,那么將新的批處理壓入隊(duì)列才有意義。 Vulkan 提供欄柵(VkFence)作為同步機(jī)制,以了解以前發(fā)送的作業(yè)是否已完成。 使用 vkCreateFence()API 創(chuàng)建欄柵對(duì)象(VkFence)。 該 API 接受一個(gè) VkFenceCreateInfo 控制結(jié)構(gòu)。

命令緩沖區(qū)在提交對(duì)象(VkSubmitInfo)中進(jìn)行指定。 該對(duì)象包含命令緩沖區(qū)列表以及一個(gè) VkSemaphore 對(duì)象,用于同步帶有若干交換鏈繪圖圖像的幀緩沖區(qū)。 這些信息會(huì)被輸入到 vkQueueSubmit()API 中;其中包含一個(gè) VkQueue 對(duì)象(命令緩沖區(qū)會(huì)被提交到其中)和一個(gè) VkFence 對(duì)象(確保在每個(gè)命令緩沖區(qū)提交之間進(jìn)行同步):

VkFenceCreateInfo fenceInfo = { ... }; VkFence drawFence;
// Create fence forensuring completion of cmdBuffer processing
vkCreateFence(device, &fenceInfo, NULL, &drawFence);

// Fill the command buffer submission control sturctures
VkSubmitInfo submitInfo[1] = { ... }; submitInfo[0].pNext   = NULL;
submitInfo[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo[0].pWaitSemaphores  = &imageAcquiredSemaphore; submitInfo[0].commandBufferCount = 1; submitInfo[0].pCommandBuffers  = &cmd;

// Queue the command buffer for execution
vkQueueSubmit(queue, 1, submitInfo, NULL);

使用展示層顯示 --- 渲染三角形

一旦命令緩沖區(qū)被提交給隊(duì)列,就會(huì)被物理設(shè)備異步處理。 因此,就會(huì)在交換鏈的繪圖表面上渲染三色三角形。 現(xiàn)在,該表面對(duì)用戶是不可見的,并且需要在顯示窗口上呈現(xiàn)出來。 繪圖表面在 VkPresentInfoKHR 控制結(jié)構(gòu)的幫助下顯示出來。 繪圖表面包含展示信息,例如應(yīng)用程序中的交換鏈數(shù)量、需要檢索的繪圖圖像的索引等。 此控制結(jié)構(gòu)對(duì)象用作 vkQueuePresentKHR 中的參數(shù)。 這會(huì)把繪圖表面圖像翻轉(zhuǎn)到顯示窗口。

注意

一旦調(diào)用 vkQueueSubmit,展示隊(duì)列就可以在它執(zhí)行展示操作之前等待,直到最后一次提交發(fā)出的 imageAcquiredSemaphore 信號(hào)量。

// Define the presentation control structure
VkPresentInfoKHR present    = { ... };
present.sType   = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext       = NULL; present.swapchainCount  = 1; present.pSwapchains    = &swapChain;
present.pImageIndices   = &swapChainObjCurrent_buffer;

// Check if all the submitted command buffers are processed
do { res=vkWaitForFences(device,1,&drawFence,VK_TRUE,FENCE_TIMEOUT);
} while (res == VK_TIMEOUT);

// Handover the swapchain image to presentation queue

// for presentation purpose
vkQueuePresentKHR(queue, &present);

// Destroy Synchronization objects vkDestroySemaphore(device, imageAcquiredSemaphore, NULL); vkDestroyFence(device, drawFence, NULL);

整合到一起

本節(jié)簡(jiǎn)要介紹我們的第一個(gè) Vulkan 偽應(yīng)用程序的工作原理。 下圖是工作模型的快照:

2-013.png

首先,應(yīng)用程序在初始階段創(chuàng)建 Vulkan 實(shí)例和設(shè)備,啟用必要的層并創(chuàng)建需要的擴(kuò)展。 該設(shè)備暴露了各種隊(duì)列(圖形隊(duì)列或計(jì)算隊(duì)列),如上圖所示。 這些隊(duì)列會(huì)收集命令緩沖區(qū)并將它們提交給物理設(shè)備進(jìn)行處理。

使用 WSI 擴(kuò)展,準(zhǔn)備繪制表面,用來渲染圖形內(nèi)容。 交換鏈會(huì)將這些繪圖表面暴露為圖像,這些圖像以圖像視圖的形式使用。 類似地,準(zhǔn)備深度圖視圖。 這些圖像視圖對(duì)象被幀緩沖區(qū) framebuffer 所使用。 渲染通道使用這個(gè)幀緩沖區(qū) framebuffer 定義一個(gè)渲染單元的操作。

命令緩沖區(qū)是從命令緩沖池中分配的,用于記錄各種命令以及 Render Pass 執(zhí)行過程。 如上圖所示,Render Pass 的執(zhí)行需要一些重要的 Vulkan 對(duì)象,例如圖形管線、描述符集、著色器模塊、管線對(duì)象和幾何數(shù)據(jù)。

最后,命令緩沖區(qū)被提交給支持展示功能的隊(duì)列,比如圖形隊(duì)列。 一旦提交,GPU 就會(huì)以異步方式進(jìn)行處理。 可能需要一些同步機(jī)制和內(nèi)存屏障才能使渲染輸出無問題。

總結(jié)

在本章中,我們探討了在系統(tǒng)上安裝 Vulkan 的步驟。 然后我們使用偽代碼編程“Hello World !!!”,在這個(gè)程序中 ,我們?cè)陲@示窗口上渲染了一個(gè)三種顏色的三角形。

這個(gè)介紹性的章節(jié)已經(jīng)將 Vulkan 的難度做了大大的簡(jiǎn)化,這樣,了解這種圖形 API 對(duì)于初學(xué)者來說也就是非常簡(jiǎn)單的事情了。 對(duì)于 Vulkan 編程來說,本章采用的是取巧代碼;可以將其作為參考,用來幫助讀者以正確的順序記憶所有的編程步驟以及它們各自對(duì)應(yīng)的 API。

亞里士多德說:“好的開始就是成功的一半!” 隨著前兩章的完成,我們?yōu)閺?0 開始全面了解 Vulkan 機(jī)制奠定了堅(jiān)實(shí)的基礎(chǔ);我們將會(huì)在后續(xù)的章節(jié)中繼續(xù)完善其內(nèi)容。

在下一章中,我們將深入研究核心編程,并開始構(gòu)建我們的第一個(gè) Vulkan 應(yīng)用程序。 您將會(huì)了解到層和擴(kuò)展,以及如何隱式和顯式地啟用它們。 我們還會(huì)研究 Vulkan 實(shí)例、設(shè)備以及隊(duì)列的基本原理,這對(duì)于與 GPU 進(jìn)行通信非常有用。 一旦我們開始編程,我們就需要查詢 GPU 暴露的資源和設(shè)施。 我們還將學(xué)習(xí)如何獲得隊(duì)列及其暴露的屬性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Vulkan 是一套革命性的高性能 3D 圖形、計(jì)算 API,適用于現(xiàn)代 GPU 管線系統(tǒng),用來滿足社區(qū)的苛刻要求...
    雨中亭_聽雨中閱讀 2,901評(píng)論 0 8
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,036評(píng)論 25 709
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,656評(píng)論 1 32
  • 貓是一種很怪的動(dòng)物。 有時(shí)候它溫順得像個(gè)剛嫁人的小媳婦,有時(shí)候它又執(zhí)拗得像個(gè)發(fā)脾氣的一根筋孩子。 幼時(shí)家里鼠患嚴(yán)重...
    刀筆伐心閱讀 477評(píng)論 0 5
  • 剛看到朋友圈有人訴說自己不喜歡孤獨(dú)害怕夜深人靜。 我卻發(fā)現(xiàn)自己越來越享受一個(gè)人的生活,看書逛街做菜玩游戲。 不喜歡...
    呂三歲同學(xué)閱讀 201評(píng)論 0 2

友情鏈接更多精彩內(nèi)容