[譯]Vulkan教程(10)交換鏈
Vulkan does not have the concept of a "default framebuffer", hence it requires an infrastructure that will own the buffers we will render to before we visualize them on the screen. This infrastructure is known as the swap chain and must be created explicitly in Vulkan. The swap chain is essentially a queue of images that are waiting to be presented to the screen. Our application will acquire such an image to draw to it, and then return it to the queue. How exactly the queue works and the conditions for presenting an image from the queue depend on how the swap chain is set up, but the general purpose of the swap chain is to synchronize the presentation of images with the refresh rate of the screen.
Vulkan沒有“默認幀緩存”的概念,因此它需要一個基礎設施,其擁有buffer,在我們將其內存顯示帶屏幕上之前,渲染之。這個基礎設施被稱為交換鏈,它必須在Vulkan中被顯式地創建。交換鏈本質上是一系列image,其等待被呈現到屏幕上。我們的app將請求這樣的一個image,在其上繪畫,然后將其返回隊列。隊列如何工作,呈現image的條件,依賴於交換鏈的配置,但是交換鏈的一般目的是同步image的呈現與屏幕的刷新。
Checking for swap chain support 檢查對交換鏈的支持
Not all graphics cards are capable of presenting images directly to a screen for various reasons, for example because they are designed for servers and don't have any display outputs. Secondly, since image presentation is heavily tied into the window system and the surfaces associated with windows, it is not actually part of the Vulkan core. You have to enable the VK_KHR_swapchain
device extension after querying for its support.
由於種種原因,不是所有的圖形卡都能直接將image呈現到屏幕上,例如如果它們是被設計用於服務器的,沒有任何顯示輸出功能。其次,由於image呈現嚴重關聯到窗口系統,surface關聯到窗口,實際上這也不是Vulkan的核心。你不得不在查詢其支持性后,啟用設備VK_KHR_swapchain
擴展。
For that purpose we'll first extend the isDeviceSuitable
function to check if this extension is supported. We've previously seen how to list the extensions that are supported by a VkPhysicalDevice
, so doing that should be fairly straightforward. Note that the Vulkan header file provides a nice macro VK_KHR_SWAPCHAIN_EXTENSION_NAME
that is defined as VK_KHR_swapchain
. The advantage of using this macro is that the compiler will catch misspellings.
為此我們首先擴展函數,檢查是否支持這個擴展。我們之前見過了如何得到一個VkPhysicalDevice
支持的擴展列表,所以現在的工作應當十分直觀。注意,Vulkan頭文件提供一個好用的宏VK_KHR_SWAPCHAIN_EXTENSION_NAME
,它被定義為VK_KHR_swapchain
。用這個宏的有點是,編譯器會捕捉到拼寫錯誤。
First declare a list of required device extensions, similar to the list of validation layers to enable.
首先聲明需要的設備擴展的列表,這與驗證層的列表相似。
const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
Next, create a new function checkDeviceExtensionSupport
that is called from isDeviceSuitable
as an additional check:
下一步,創建新函數checkDeviceExtensionSupport
,在isDeviceSuitable
中調用它:
bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); bool extensionsSupported = checkDeviceExtensionSupport(device); return indices.isComplete() && extensionsSupported; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { return true; }
Modify the body of the function to enumerate the extensions and check if all of the required extensions are amongst them.
修改函數內容,枚舉擴展,檢查是否所有要求的擴展都在這里面。
bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector<VkExtensionProperties> availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); for (const auto& extension : availableExtensions) { requiredExtensions.erase(extension.extensionName); } return requiredExtensions.empty(); }
I've chosen to use a set of strings here to represent the unconfirmed required extensions. That way we can easily tick them off while enumerating the sequence of available extensions. Of course you can also use a nested loop like in checkValidationLayerSupport
. The performance difference is irrelevant. Now run the code and verify that your graphics card is indeed capable of creating a swap chain. It should be noted that the availability of a presentation queue, as we checked in the previous chapter, implies that the swap chain extension must be supported. However, it's still good to be explicit about things, and the extension does have to be explicitly enabled.
我用一組字符串來代表為確認的需要的擴展。這樣我們可以容易地在枚舉需要的擴展時剔除它們。當然你也可以用內嵌循環,像在checkValidationLayerSupport
函數中那樣。性能差異是不相干的。現在運行代碼,驗證你的圖形卡確實支持創建交換鏈。要注意到,一個presentation隊列的功能(我們在之前的章節檢查過的)暗示了交換鏈擴展必須被支持。但是,顯示地檢查一下還是好的,且這個擴展也必須被顯式地啟用。
Enabling device extensions 啟用設備擴展
Using a swapchain requires enabling the VK_KHR_swapchain
extension first. Enabling the extension just requires a small change to the logical device creation structure:
使用交換鏈,首先要啟用VK_KHR_swapchain
擴展。啟用這個擴展,值要求對邏輯設備的創建結構體做一點修改:
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
Make sure to replace the existing line createInfo.enabledExtensionCount = 0;
when you do so.
確保替換這一行createInfo.enabledExtensionCount = 0;
。
Querying details of swap chain support 查詢交換鏈的細節
Just checking if a swap chain is available is not sufficient, because it may not actually be compatible with our window surface. Creating a swap chain also involves a lot more settings than instance and device creation, so we need to query for some more details before we're able to proceed.
僅僅檢查交換鏈是否可用,不夠高效,因為它可能不與我們的窗口surface兼容。創建交換鏈也設計很多配置,比instance和設備創建的配置還多,所以我們需要查詢一些細節,然后再進行下一步。
There are basically three kinds of properties we need to check:
- Basic surface capabilities (min/max number of images in swap chain, min/max width and height of images)
- Surface formats (pixel format, color space)
- Available presentation modes
我們需要至少檢查3種屬性:
- 基礎surface功能(交換鏈包含的image的最大\小數量,image的寬度和高度的最大\最小值)
- Surface格式(像素格式,顏色空間)
- 可用的presentation模式
Similar to findQueueFamilies
, we'll use a struct to pass these details around once they've been queried. The three aforementioned types of properties come in the form of the following structs and lists of structs:
與findQueueFamilies
相似,我們將用一個結構體傳入這些細節信息。上述3種屬性按如下方式陳列:
struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes; };
We'll now create a new function querySwapChainSupport
that will populate this struct.
我們現在要創建一個新函數querySwapChainSupport
,它返回這個結構體。
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { SwapChainSupportDetails details; return details; }
This section covers how to query the structs that include this information. The meaning of these structs and exactly which data they contain is discussed in the next section.
這一節講了如何查詢結構體。這些結構體的含義和每個數據的含義將在后續小節討論。
Let's start with the basic surface capabilities. These properties are simple to query and are returned into a single VkSurfaceCapabilitiesKHR
struct.
我們從基礎surface功能開始。這些屬性很容易查詢,且包含在返回的VkSurfaceCapabilitiesKHR
結構體。
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
This function takes the specified VkPhysicalDevice
and VkSurfaceKHR
window surface into account when determining the supported capabilities. All of the support querying functions have these two as first parameters because they are the core components of the swap chain.
這個函數在判定支持的功能時,考慮了VkPhysicalDevice
和VkSurfaceKHR
窗口surface。所有的支持查詢函數都有這2個參數,因為它們是交換鏈的核心組件。
The next step is about querying the supported surface formats. Because this is a list of structs, it follows the familiar ritual of 2 function calls:
下一步,要查詢支持的surface格式。因為這是結構體的列表,它遵循2個函數調用的熟悉的慣例:
uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); }
Make sure that the vector is resized to hold all the available formats. And finally, querying the supported presentation modes works exactly the same way with vkGetPhysicalDeviceSurfacePresentModesKHR
:
確保數組大小被重新設定,以記錄所有可用的格式。最后,查詢支持的presentation模式與vkGetPhysicalDeviceSurfacePresentModesKHR
的工作方式嚴格相同。
uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); }
All of the details are in the struct now, so let's extend isDeviceSuitable
once more to utilize this function to verify that swap chain support is adequate. Swap chain support is sufficient for this tutorial if there is at least one supported image format and one supported presentation mode given the window surface we have.
所有細節都在下面的結構體中,所以我們再擴展isDeviceSuitable
一次,以驗證勝任本教程需要的對交換鏈的支持。如果至少支持一個image格式和一個presentation模式,交換鏈支持是足夠的。
bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); }
It is important that we only try to query for swap chain support after verifying that the extension is available. The last line of the function changes to:
重要的一點是,我們只在驗證了擴展可用后,嘗試查詢交換鏈支持。函數的最后一行修改為:
return indices.isComplete() && extensionsSupported && swapChainAdequate;
Choosing the right settings for the swap chain 為交換鏈選擇正確的設置
If the swapChainAdequate
conditions were met then the support is definitely sufficient, but there may still be many different modes of varying optimality. We'll now write a couple of functions to find the right settings for the best possible swap chain. There are three types of settings to determine:
- Surface format (color depth)
- Presentation mode (conditions for "swapping" images to the screen)
- Swap extent (resolution of images in swap chain)
如果swapChainAdequate
條件符合,那么支持就是絕對高效的,但是可能還有很多不同的模式可供優化。我們現在寫一些函數,以找到最好的交換鏈的正確的設置。有3個類型的設置:
- Surface格式(顏色 深度)
- Presentation模式(“交換”image到屏幕的條件)
- 交換擴展(交換鏈中image的解析度)
For each of these settings we'll have an ideal value in mind that we'll go with if it's available and otherwise we'll create some logic to find the next best thing.
對每個設置,如果可以的話,我們將用一個理想值,否則我們就創建一些邏輯來找到次優的。
Surface format surface格式
The function for this setting starts out like this. We'll later pass the formats
member of the SwapChainSupportDetails
struct as argument.
設置這個的函數開始長這樣。我們后續會傳入SwapChainSupportDetails
的成員formats
作為參數。
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { }
Each VkSurfaceFormatKHR
entry contains a format
and a colorSpace
member. The format
member specifies the color channels and types. For example, VK_FORMAT_B8G8R8A8_UNORM
means that we store the B, G, R and alpha channels in that order with an 8 bit unsigned integer for a total of 32 bits per pixel. The colorSpace
member indicates if the SRGB color space is supported or not using the VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
flag. Note that this flag used to be called VK_COLORSPACE_SRGB_NONLINEAR_KHR
in old versions of the specification.
每個條目VkSurfaceFormatKHR
,包含一個format
和一個colorSpace
成員。format
成員表面顏色通道和類型。例如,VK_FORMAT_B8G8R8A8_UNORM
的意思是,我們按BRG和alpha通道的順序保存,每個通道占8位,每個像素一共占32位。colorSpace
成員用VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
標志標明SRGB顏色空間是否被支持。注意,這個標志在老版說明書里被稱為VK_COLORSPACE_SRGB_NONLINEAR_KHR
。
For the color space we'll use SRGB if it is available, because it results in more accurate perceived colors. Working directly with SRGB colors is a little bit challenging, so we'll use standard RGB for the color format, of which one of the most common ones is VK_FORMAT_B8G8R8A8_UNORM
.
如果可用,我們的顏色空間將使用SRGB,因為它給出更精確的感知顏色。直接用SRGB顏色工作,有點挑戰性,所以我們用標准RGB作為顏色格式,其中最常用的一個格式是VK_FORMAT_B8G8R8A8_UNORM
。
Let's go through the list and see if the preferred combination is available:
我們過一遍列表,看看想要的組合是否可用:
for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } }
If that also fails then we could start ranking the available formats based on how "good" they are, but in most cases it's okay to just settle with the first format that is specified.
如果這也失敗了,那么我們可以開始為可用的格式排序(基於它們有多“好”),但是大多數情況下,用第一個格式就可以。
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } return availableFormats[0]; }
Presentation mode presentation模式
The presentation mode is arguably the most important setting for the swap chain, because it represents the actual conditions for showing images to the screen. There are four possible modes available in Vulkan:
VK_PRESENT_MODE_IMMEDIATE_KHR
: Images submitted by your application are transferred to the screen right away, which may result in tearing.VK_PRESENT_MODE_FIFO_KHR
: The swap chain is a queue where the display takes an image from the front of the queue when the display is refreshed and the program inserts rendered images at the back of the queue. If the queue is full then the program has to wait. This is most similar to vertical sync as found in modern games. The moment that the display is refreshed is known as "vertical blank".VK_PRESENT_MODE_FIFO_RELAXED_KHR
: This mode only differs from the previous one if the application is late and the queue was empty at the last vertical blank. Instead of waiting for the next vertical blank, the image is transferred right away when it finally arrives. This may result in visible tearing.VK_PRESENT_MODE_MAILBOX_KHR
: This is another variation of the second mode. Instead of blocking the application when the queue is full, the images that are already queued are simply replaced with the newer ones. This mode can be used to implement triple buffering, which allows you to avoid tearing with significantly less latency issues than standard vertical sync that uses double buffering.
Presentation模式是交換鏈配置中最重要的一個,因為它代表了呈現image到屏幕的條件。Vulkan中有4個可能的模式:
VK_PRESENT_MODE_IMMEDIATE_KHR
:你的app提交的image會被立即傳送到屏幕上,這可能導致撕裂。VK_PRESENT_MODE_FIFO_KHR
:交換鏈是一個隊列,顯示器刷新時,從隊列頭部拿一個image,程序將渲染好的image放到隊列尾部。如果隊列滿了,程序就必須等待。這與現代游戲中的垂直同步最相似。顯示器被刷新的時刻被稱為“垂直回歸”。VK_PRESENT_MODE_FIFO_RELAXED_KHR
:只有當垂直回歸結束后,app晚了,隊列空了,這一模式才與上一個模式有所區別。它不等待下一次垂直回歸,而是當image到達時立即傳送。這可能導致可見的撕裂。VK_PRESENT_MODE_MAILBOX_KHR
:這是第二個模式的另一個變種。隊列滿的時候,它不是阻塞app,而是隊列中的image直接就被新的替換掉了。這個模式可以被用於實現三緩存,其允許你避免撕裂,且大幅減少延遲問題(與雙緩存的垂直同步模式相比)。
Only the VK_PRESENT_MODE_FIFO_KHR
mode is guaranteed to be available, so we'll again have to write a function that looks for the best mode that is available:
只有VK_PRESENT_MODE_FIFO_KHR
模式是絕對可用的,所以我們再次不得不寫個函數,其查詢可用的最佳模式:
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { return VK_PRESENT_MODE_FIFO_KHR; }
I personally think that triple buffering is a very nice trade-off. It allows us to avoid tearing while still maintaining a fairly low latency by rendering new images that are as up-to-date as possible right until the vertical blank. So, let's look through the list to see if it's available:
我個人認為,三緩存是非常好的平衡之術。它允許我們避免撕裂,且仍舊保持了相當低的延遲(通過盡可能快地渲染新的image,直到垂直回歸)。所以,讓我們過一下列表,看看他是否可用:
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; } } return VK_PRESENT_MODE_FIFO_KHR; }
Unfortunately some drivers currently don't properly support VK_PRESENT_MODE_FIFO_KHR
, so we should prefer VK_PRESENT_MODE_IMMEDIATE_KHR
if VK_PRESENT_MODE_MAILBOX_KHR
is not available:
不幸的是,目前有的driver不支持VK_PRESENT_MODE_FIFO_KHR
,所以當VK_PRESENT_MODE_MAILBOX_KHR
不可用時,我們傾向於VK_PRESENT_MODE_IMMEDIATE_KHR
:
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { bestMode = availablePresentMode; } } return bestMode; }
Swap extent 交換外延
That leaves only one major property, for which we'll add one last function:
只剩下一個主要屬性了,我們添加最后一個函數:
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { }
The swap extent is the resolution of the swap chain images and it's almost always exactly equal to the resolution of the window that we're drawing to. The range of the possible resolutions is defined in the VkSurfaceCapabilitiesKHR
structure. Vulkan tells us to match the resolution of the window by setting the width and height in the currentExtent
member. However, some window managers do allow us to differ here and this is indicated by setting the width and height in currentExtent
to a special value: the maximum value of uint32_t
. In that case we'll pick the resolution that best matches the window within the minImageExtent
and maxImageExtent
bounds.
交換外延是交換鏈image的解析度,它幾乎總數等於我們要繪制到的窗口的解析度。可能的解析度范圍由VkSurfaceCapabilitiesKHR
結構體定義。Vulkan告訴我們,通過在currentExtent
成員中設置寬度和高度來匹配窗口的解析度。但是,有的窗口管理器確實允許我們有所不同,這可以通過將設置為一個特殊值來標示:uint32_t
的最大值。此時,我們將拾取最匹配窗口的解析度,其以minImageExtent
和maxImageExtent
為邊界。
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) { return capabilities.currentExtent; } else { VkExtent2D actualExtent = {WIDTH, HEIGHT}; actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); return actualExtent; } }
The max
and min
functions are used here to clamp the value of WIDTH
and HEIGHT
between the allowed minimum and maximum extents that are supported by the implementation. Make sure to include the <algorithm>
header to use them.
max
和min
函數這里用於裁切WIDTH
和HEIGHT
的值,使其置於實現允許的最大值和最小值之間。為了使用它們,確保include<algorithm>
頭文件。
Creating the swap chain 創建交換鏈
Now that we have all of these helper functions assisting us with the choices we have to make at runtime, we finally have all the information that is needed to create a working swap chain.
既然我們有了所有的輔助函數,幫我們在運行時做出選擇,我們終於得到了所有需要用於創建交換鏈的信息。
Create a createSwapChain
function that starts out with the results of these calls and make sure to call it from initVulkan
after logical device creation.
創建一個函數,它記錄這些調用的結果。確保在initVulkan
中邏輯創建創建函數之后調用它。
void initVulkan() { createInstance(); setupDebugCallback(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); } void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); }
Aside from these properties we also have to decide how many images we would like to have in the swap chain. The implementation specifies the minimum number that it requires to function:
除了這些屬性,我們也不得不決定在交換鏈中要有多少個image。實現標明了它需要的最小值:
uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
However, simply sticking to this minimum means that we may sometimes have to wait on the driver to complete internal operations before we can acquire another image to render to. Therefore it is recommended to request at least one more image than the minimum:
但是,簡單地使用這個最小值,意味着在請求另一個image以渲染前,我們可能有時候不得不等待driver完成內部操作。因此推薦請求至少比最小值多1個的image:
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
We should also make sure to not exceed the maximum number of images while doing this, where 0
is a special value that means that there is no maximum:
我們應該也確保不超過image的最大值,而0
是個特殊值,意思是沒有最大值:
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { imageCount = swapChainSupport.capabilities.maxImageCount; }
As is tradition with Vulkan objects, creating the swap chain object requires filling in a large structure. It starts out very familiarly:
作為Vulkan對象的傳統,創建交換鏈對象要求填入一個巨大的結構體。開始時似曾相識:
VkSwapchainCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface;
After specifying which surface the swap chain should be tied to, the details of the swap chain images are specified:
在標示了交換鏈要綁定到哪個surface后,要說明交換鏈image的細節:
createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
The imageArrayLayers
specifies the amount of layers each image consists of. This is always 1
unless you are developing a stereoscopic 3D application. The imageUsage
bit field specifies what kind of operations we'll use the images in the swap chain for. In this tutorial we're going to render directly to them, which means that they're used as color attachment. It is also possible that you'll render images to a separate image first to perform operations like post-processing. In that case you may use a value like VK_IMAGE_USAGE_TRANSFER_DST_BIT
instead and use a memory operation to transfer the rendered image to a swap chain image.
imageArrayLayers
標明了每個image包含的層的數量。除非你在開發體視3D應用程序,它總是1
。imageUsage
位字段標明我們將用交換鏈中的image在哪種操作上。本教程中我們將直接在上面渲染,這意味着它們用於顏色附件。也可能你先將渲染圖片到單獨的image,然后實施后處理之類的操作,此時你可能用VK_IMAGE_USAGE_TRANSFER_DST_BIT
這樣的值,且用一個內存操作來轉移渲染到圖片到一個交換鏈image。
QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.queueFamilyIndexCount = 0; // Optional createInfo.pQueueFamilyIndices = nullptr; // Optional }
Next, we need to specify how to handle swap chain images that will be used across multiple queue families. That will be the case in our application if the graphics queue family is different from the presentation queue. We'll be drawing on the images in the swap chain from the graphics queue and then submitting them on the presentation queue. There are two ways to handle images that are accessed from multiple queues:
VK_SHARING_MODE_EXCLUSIVE
: An image is owned by one queue family at a time and ownership must be explicitly transfered before using it in another queue family. This option offers the best performance.VK_SHARING_MODE_CONCURRENT
: Images can be used across multiple queue families without explicit ownership transfers.
下一步,我們需要標明如何出列交換鏈中的image,它們將被用於多個隊列家族中。如果一個圖形隊列家族與presentation隊列不一樣,image就將被用於多個隊列家族中。我們將在交換鏈的image中在圖形隊列中繪制圖片,然后提交到presentation隊列。有2種方法來處理多隊列讀寫的image:
VK_SHARING_MODE_EXCLUSIVE
:一個image在同一時間只能屬於一個隊列家族,所有權必須被顯式地轉移后,才能在另一個隊列家族中使用。這個選項提供最佳性能。VK_SHARING_MODE_CONCURRENT
:。Image可以在多個隊列家族使用,無需顯式地轉移所有權。
If the queue families differ, then we'll be using the concurrent mode in this tutorial to avoid having to do the ownership chapters, because these involve some concepts that are better explained at a later time. Concurrent mode requires you to specify in advance between which queue families ownership will be shared using the queueFamilyIndexCount
and pQueueFamilyIndices
parameters. If the graphics queue family and presentation queue family are the same, which will be the case on most hardware, then we should stick to exclusive mode, because concurrent mode requires you to specify at least two distinct queue families.
如果隊列家族不同,那么本教程將用並發模式,以避免再開個所有權章節,因為這些涉及到一些概念,晚一點再解釋它們更好。並發模式要求你用queueFamilyIndexCount
和pQueueFamilyIndices
參數,提前標明,所有權將在哪些隊列家族中共享。如果圖形隊列家族和presentation隊列家族相同(大多數硬件上都是這樣),那么我們就用exclusive模式,因為並發模式要求你標明至少2個不同的隊列家族。
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
We can specify that a certain transform should be applied to images in the swap chain if it is supported (supportedTransforms
in capabilities
), like a 90 degree clockwise rotation or horizontal flip. To specify that you do not want any transformation, simply specify the current transformation.
如果某個變換受支持(capabilities
中的supportedTransforms
),例如順時針選擇90度或水平翻轉,我們可以標明讓它應用到交換鏈中的image上。為標明這個,你不用費別的勁,簡單地標明當前當前的變換即可。
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
The compositeAlpha
field specifies if the alpha channel should be used for blending with other windows in the window system. You'll almost always want to simply ignore the alpha channel, hence VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
.
字段compositeAlpha
標明,alpha通道是否應當被用於與窗口系統中的其他窗口混合。你將幾乎總是想要簡單地忽略這個alpha通道,因此選擇VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
。
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
The presentMode
member speaks for itself. If the clipped
member is set to VK_TRUE
then that means that we don't care about the color of pixels that are obscured, for example because another window is in front of them. Unless you really need to be able to read these pixels back and get predictable results, you'll get the best performance by enabling clipping.
presentMode
成員是不言自明的。如果clipped
成員設置為VK_TRUE
,那么那意味着我們不關心被遮擋的像素的顏色,例如當其他窗口位於它們前面時。除非你陣的需要讀這些像素,得到可預測的結果,否則你將通過啟用裁剪來得到最后的性能。
createInfo.oldSwapchain = VK_NULL_HANDLE;
That leaves one last field, oldSwapChain
. With Vulkan it's possible that your swap chain becomes invalid or unoptimized while your application is running, for example because the window was resized. In that case the swap chain actually needs to be recreated from scratch and a reference to the old one must be specified in this field. This is a complex topic that we'll learn more about in a future chapter. For now we'll assume that we'll only ever create one swap chain.
還有最后一個字段,oldSwapChain
。在你的app運行過程中,在Vulkan中有可能你的交換鏈變得無效或不夠優化,例如梵蒂岡窗口大小改變時。此時,交換鏈實際上需要被重新創建,一個對舊交換鏈的引用就必須寫入此字段。這是個復雜的話題,我們將在未來的篇章學習更多。現在我們假設我們只創建一次交換鏈。
Now add a class member to store the VkSwapchainKHR
object:
現在添加成員以保存VkSwapchainKHR
對象:
VkSwapchainKHR swapChain;
Creating the swap chain is now as simple as calling vkCreateSwapchainKHR
:
創建交換鏈很簡單,調用vkCreateSwapchainKHR
即可:
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); }
The parameters are the logical device, swap chain creation info, optional custom allocators and a pointer to the variable to store the handle in. No surprises there. It should be cleaned up using vkDestroySwapchainKHR
before the device:
參數是邏輯設備,交換鏈創建信息,可選的自定義內存申請函數和一個保存句柄的變量的指針。沒有新鮮事。它應該在銷毀設備前被vkDestroySwapchainKHR
銷毀:
void cleanup() { vkDestroySwapchainKHR(device, swapChain, nullptr); ... }
Now run the application to ensure that the swap chain is created successfully! If at this point you get an access violation error in vkCreateSwapchainKHR
or see a message like Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll
, then see the FAQ entry about the Steam overlay layer.
現在運行程序以確保交換鏈被成功創建了。如果此時你在vkCreateSwapchainKHR
碰到存取微觀錯誤,或看到這樣的消息:Failed to find 'vkGetInstanceProcAddress' in layer SteamOverlayVulkanLayer.dll
,那么查閱FAQ記錄中關於流覆蓋層的問題。
Try removing the createInfo.imageExtent = extent;
line with validation layers enabled. You'll see that one of the validation layers immediately catches the mistake and a helpful message is printed:
嘗試去掉createInfo.imageExtent = extent;
這一行,啟用驗證層,你即將看到,一個驗證 理解捕捉了這個錯誤,打印出有幫助的消息:
Retrieving the swap chain image 檢索交換鏈image
The swap chain has been created now, so all that remains is retrieving the handles of the VkImage
s in it. We'll reference these during rendering operations in later chapters. Add a class member to store the handles:
現在交換鏈已經創建了,剩下的就是檢索其中VkImage
的句柄。我們將在渲染操作中(后續章節)引用這些句柄。添加成員以保存這些句柄:
std::vector<VkImage> swapChainImages;
The images were created by the implementation for the swap chain and they will be automatically cleaned up once the swap chain has been destroyed, therefore we don't need to add any cleanup code.
這些image是實現為交換鏈創建的,一旦交換鏈被銷毀,它們將被自動地清理,因此我們不需添加任何清理代碼。
I'm adding the code to retrieve the handles to the end of the createSwapChain
function, right after the vkCreateSwapchainKHR
call. Retrieving them is very similar to the other times where we retrieved an array of objects from Vulkan. Remember that we only specified a minimum number of images in the swap chain, so the implementation is allowed to create a swap chain with more. That's why we'll first query the final number of images with vkGetSwapchainImagesKHR
, then resize the container and finally call it again to retrieve the handles.
我將檢索句柄的代碼發那個到createSwapChain
函數的最后,在調用vkCreateSwapchainKHR
之后。檢索它們與我們檢索Vulkan對象數組時很相似。回憶一下,我們只標明了交換鏈中image的最小值,所以實現可以創建更多image。這就是為什么我們先用vkGetSwapchainImagesKHR
查詢image的數量,然后調整數組大小,最后再次調用它來檢索句柄。
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
One last thing, store the format and extent we've chosen for the swap chain images in member variables. We'll need them in future chapters.
最后預計算,保存我們為交換鏈image選擇的格式和外延到成員變量中。我們在以后的章節會需要它們。
VkSwapchainKHR swapChain; std::vector<VkImage> swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; ... swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent;
We now have a set of images that can be drawn onto and can be presented to the window. The next chapter will begin to cover how we can set up the images as render targets and then we start looking into the actual graphics pipeline and drawing commands!
現在我們有了image,其可被繪畫,可被呈現到窗口。下一章將開始講述我們如何設置image為渲染目標,然后開始深入研究實際的圖形管道和繪制命令!
C++ code C++代碼
- Previous上一章
- Next下一章