CSharpGL(57)[譯]Vulkan清空屏幕

CSharpGL(57)[譯]Vulkan清空屏幕html

本文是對(http://ogldev.atspace.co.uk/www/tutorial51/tutorial51.html)的翻譯,做爲學習Vulkan的一次嘗試。數據庫

不翻譯的話,每次都在看第一句,那就學不完了。數組

Background 背景

+BIT祝威+悄悄在此留下版了個權的信息說:

Welcome back. I hope that you've been able to complete the previous tutorial successfully and you are now ready to continue. In this tutorial we will do a very basic operation that usually starts a new frame - clear the screen. In OpenGL this can be done very easily with just the glClear() command but as you can already assume - it's a totally different ball game with Vulkan. This tutorial will introduce us to three new and improtant Vulkan entities - swap chain, images and the command buffers.緩存

歡迎回來。我但願你已經完成了上一篇教程,如今準備好繼續了。本教程中咱們將作一個很是基本的操做——在開啓新的一幀時首先作的——清空屏幕。在OpenGL中,這能夠直接簡單地用glClear()命令完成,可是你可能已經想到了——這在Vulkan中是徹底不一樣的事。本教程將介紹給你們3個新的重要的Vulkan實體——交換鏈,Image和命令緩存。安全

Let's look at a very simple OpenGL render loop which just clears the screen:性能優化

+BIT祝威+悄悄在此留下版了個權的信息說:

咱們來看看OpenGL的一個簡單的渲染循環——僅僅清空屏幕:多線程

1 void RenderLoop() 2 { 3  glClear(GL_COLOR_BUFFER_BIT); 4     glutSwapBuffers();   // Or in GLFW: glfwSwapBuffers(pWindow);
5 }

 

What we have here is a GL command to clear the color buffer followed by a GLUT or GLFW call that swaps the front buffer which is currently being displayed with the back buffer (which is really the buffer that glClear targeted). These two seemingly innocent functions hide a ton of back stage activity by the OpenGL driver. What Vulkan does is to provide us with a standard interface to the low level operations that used to be the sole domain of the OpenGL driver. Now we have to take control and manage these back stage activities ourselves.併發

這段代碼裏,咱們用一個GL命令清空顏色緩存,而後用一個GLUT或GLFW調用來交換front緩存(當前在顯示的)與back緩存(glClear清空的)。這兩個看起來弱弱的函數隱藏了OpenGL驅動的成噸的後臺活動。Vulkan提供給咱們的,是一個對低層操做的接口,之前這是屬於OpenGL驅動的獨有領域。如今你必須本身來管理控制這些後臺活動了。app

+BIT祝威+悄悄在此留下版了個權的信息說:

So now let's think what really happens in the driver when it executes that render loop. In most graphics drivers there is a concept of a command buffer. The command buffer is a memory buffer that the driver fills with GPU instructions. The driver translates the GL commands into GPU instructions. It submits the command buffer to the GPU and there is usually some form of a queue of all the command buffers that are currently in flight. The GPU picks up the command buffers one by one and executes their contents. The command buffers contain instructions, pointers to resources, state changes and everything else that the GPU needs in order to execute the the OpenGL commands correctly. Each command buffer can potentially contain multiple OpenGL commands (and it usually does because it is more efficient). It is up to the driver to decide how to batch the OpenGL commands into the command buffers. The GPU informs the driver whenever a command buffer is completed and the driver can stall the application to prevent it from getting too much ahead of the GPU (e.g. the GPU renders frame N while the application is already at frame N+10).less

因此如今咱們來想一想,驅動在執行那個渲染循環時到底發生了什麼。大多數圖形卡里都有一個「命令緩存」的概念。命令緩存,是驅動填入了GPU指令的一塊內存。驅動將GL命令翻譯成GPU指令。驅動將命令緩存提交給GPU,正在運行的全部的命令緩存一般造成某種形式的隊列。GPU一個一個地拿起命令緩存,執行它們的內容。命令緩存包換指令、對資源的指針、狀態變化和任何執行OpenGL命令所需的東西。每一個命令緩存均可能包含多個OpenGL命令(一般是這樣,由於更高效)。驅動決定如何將OpenGL命令批發爲命令緩存。命令緩存完成後,GPU就通知驅動,驅動就暫停應用程序,以免它領先GPU太多(例如GPU渲染幀N,同時應用程序已經在幀N+10了)。

This model works pretty well. Why do we need to change it? Well, making the driver in charge of command buffer management prevents us from some important potential performance optimizations that only we can make. For example, consider the Mesh class that we developed in previous tutorials when we studied the Assimp library. Rendering a mesh meant that in each frame we had to submit the same group of draw commands when the only real change was a few matrices that controlled the transformation. For each draw command the driver had to do considerable amount of work which is a waste of time in each frame. What if we could create a command buffer for this mesh class ahead of time and just submit it in each frame (while changing the matrices somehow)? That's the whole idea behind Vulkan. For the OpenGL driver the frame is just a series of GL commands and the driver doesn't understand what exactly the application is doing. It doesn't even know that these commands will be repeated in the next frame. Only the app designer understands what's going on and can create command buffers in a way that will match the structure of the application.

這個模型工做得至關好。爲何咱們要改變它?好吧,讓驅動來負責管理命令緩存,可讓咱們失去只有咱們能作到的重要的性能優化機會。例如,考慮咱們在以前的教程中學習Assimp庫時開發的網格類。渲染一個網格,意味着對每一個陣咱們必須相同的渲染命令,而真正有所改變的僅僅是用於控制方位變換的矩陣。對每一個渲染命令,驅動必須作可觀的工做量,這在每一幀裏都形成了浪費。若是咱們能爲這個網格類建立一個命令緩存,每一幀裏提交它(同時想辦法修改矩陣)該多好?這就是Vulkan的核心思想。對OpenGL驅動來講,一幀只是一系列GL命令,驅動不理解應用程序到底在作什麼。它甚至不知道這些命令會在下一幀重複。只有app設計者理解在發生什麼,只有他能建立命令緩存,且使之與應用程序的結構相適應。

Another area where OpenGL never excelled in is multi threading. Submitting draw commands in different threads is possible but complex. The problem is that OpenGL was not designed with multi threading in mind. Therefore, in most cases a graphics app has one rendering thread and uses multi-threading for the rest of the logic. Vulkan addresses multi threading by allowing you to build command buffers concurrently and introduces the concept of queues and semaphores to handle concurrency at the GPU level.

OpenGL不擅長的另外一個領域就是多線程。在不一樣的線程裏提供渲染命令是可能的,可是很複雜。問題在於OpenGL在設計之初就沒有考慮要支持多線程。所以,大多數時候一個圖形app有1個渲染線程和多個邏輯線程。Vulkan支持多線程:它容許你併發地構建命令緩存,引入了queue和semaphore的概念來處理GPU層次的併發問題。

Let's get back to that render loop. By now you can imagine that what we are going to do is create a command buffer and add the clear instruction to it. What about swap buffers? We have been using GLUT/GLFW so we never gave much thought about it. GLUT/GLFW are not part of OpenGL. They are libraries built on top of windowing APIs such as GLX (Linux), WGL (Windows), EGL (Android) and CGL (Mac). They make it easy to build OS independent OpenGL programs. If you use the underlying APIs directly you will have to create an OpenGL context and window surface which are in general corresponding to the instance and surface we created in the previous tutorial. The underlying APIs provide functions such as glXSwapBuffers() and eglSwapBuffers() in order to swap the front and back buffers that are hidden under the cover of the surface. They don't provide you much control beyond that.

咱們回到那個渲染循環。如今你能夠想象咱們計劃作的,就是建立一個命令緩存,給它加入清空指令。那麼swap緩存呢?咱們一直在用GLUT/GLFW,因此沒有好好考慮過它。GLUT/GLFW不是OpenGL的一部分。它們是基於窗口API(例如Linux的GLX、Windows的WGL、Android的EGL和Mac的CGL)的庫。它們讓構建操做系統無關的OpenGl程序簡單了。若是你直接使用底層API,你將不得不建立OpenGL上下文和窗口surface,它們通常都是與咱們在以前教程中建立的instance和surface對應的。底層API提供glXSwapBuffers()和eglSwapBuffers()這樣的函數,用於交換front和back緩存。除此以外它們沒有提供給你更多的控制權。

Vulkan goes a step further by introducing the concepts of swap chain, images and presentation engine. The Vulkan spec describes the swap chain as an abstraction of an array of presentable images that are associated with the surface. The images are what is actually being displayed on the screen and only one can be displayed at a time. When an image is displayed the application is free to prepare the remaining images and queue them for presentation. The total number of images can also be controlled by the application.

+BIT祝威+悄悄在此留下版了個權的信息說:

Vulkan走得更遠,它引入了交換鏈、image和表現引擎的概念。Vulkan說明書將交換鏈描述爲關聯到surface的可顯示image的數組的抽象。這些image實際上就是顯示到屏幕上的東西,同一時間只能有1個顯示出來。當一個image被顯示時,應用程序能夠自由準備其餘image,並將其列隊待用。Image總數也能夠被應用程序控制。

The presentation engine represents the display on the platform. It is responsible for picking up images from the queue, presenting them on the screen and notifying the application when an image can be reused.

表現引擎表明在平臺上的顯示。他負責從隊列裏拿起image,提交給屏幕,當image能夠被重用時通知應用程序。

Now that we understand these concepts let's review what we need add to the previous tutorial in order to make it clear the screen. Here's the one time initialization steps:

既然咱們理解了這些概念,咱們來評審一下須要加入上一個教程的東西,以便清空屏幕。下面是一次性初始化的步驟:

  1. Get the command buffer queue from the logical device. Remember that the device create info included an array of VkDeviceQueueCreateInfo structures with the number of queues from each family to create. For simplicity we are using just one queue from graphics family. So this queue was already created in the previous tutorial. We just need to get its address.
    從logical device獲得命令緩存。記住,device建立信息中包含一個VkDeviceQueueCreateInfo結構體和queue編號。簡單來講,咱們只用圖形family的一個queue。這個queue是已經在上一個教程中建立好了的。咱們只需找到它的地址。
  2. Create the swap chain and get the handles to its images.
    建立交換鏈,獲得它的image的句柄。
  3. Create a command buffer and add the clear instruction to it.
    建立命令緩存,添加清空指令。

And here's what we need to do in the render loop:

這裏是咱們須要在渲染循環中作的:

  1. Acquire the next image from the swap chain.
    從交換鏈中請求下一個image。
  2. Submit the command buffer.
    提交命令緩存。
  3. Submit a request to present the image.
    提交「顯示image」的請求。

Now let's review the code to accomplish this.

如今咱們來評審一下相關代碼。

Source walkthru 源代碼瀏覽

All the logic that needs to be developed for this tutorial will go into the following class:

+BIT祝威+悄悄在此留下版了個權的信息說:

本教程中全部須要開發的邏輯都在下述類中:

 1 class OgldevVulkanApp  2 {  3 public:  4 
 5     OgldevVulkanApp(const char* pAppName);  6     
 7     ~OgldevVulkanApp();  8     
 9     void Init(); 10     
11     void Run(); 12     
13 private: 14 
15     void CreateSwapChain(); 16     void CreateCommandBuffer(); 17     void RecordCommandBuffers(); 18     void RenderScene(); 19 
20     std::string m_appName; 21     VulkanWindowControl* m_pWindowControl; 22  OgldevVulkanCore m_core; 23     std::vector<VkImage> m_images; 24  VkSwapchainKHR m_swapChainKHR; 25  VkQueue m_queue; 26     std::vector<VkCommandBuffer> m_cmdBufs; 27  VkCommandPool m_cmdBufPool; 28 };

 

What we have here are a couple of public functions (Init() and Run()) that will be called from main() later on and several private member functions that are based on the steps that were described in the previous section. In addition, there are a few private member variables. The VulkanWindowControl and OgldevVulkanCore which were part of the main() function in the previous tutorial were moved here. We also have a vector of images, swap chain object, command queue, vector of command buffers and a command buffer pool. Now let's look at the Init() function:

+BIT祝威+悄悄在此留下版了個權的信息說:

上述代碼中是2個public函數(Init()和Run()),它們將被main()函數調用;還有幾個private函數,是咱們在上一篇教程中涉及的。另外,還有幾個private成員變量。上一篇教程中的VulkanWindowControl和OgldevVulkanCore在這裏被移除了。咱們還有image數組、交換鏈對象、命令隊列、命令緩存數組和命令緩存池。如今咱們來看看Init()函數:

 1 void OgldevVulkanApp::Init()  2 {  3 #ifdef WIN32  4     m_pWindowControl = new Win32Control(m_appName.c_str());  5 #else            
 6     m_pWindowControl = new XCBControl();  7 #endif    
 8     m_pWindowControl->Init(WINDOW_WIDTH, WINDOW_HEIGHT);  9 
10  m_core.Init(m_pWindowControl); 11         
12     vkGetDeviceQueue(m_core.GetDevice(), m_core.GetQueueFamily(), 0, &m_queue); 13 
14  CreateSwapChain(); 15  CreateCommandBuffer(); 16  RecordCommandBuffers(); 17 }

 

This function starts in a similar fashion to the previous tutorial by creating and initializing the window control and Vulkan core objects. After that we call the private members to create the swap chain and command buffer and to record the clear instruction into the command buffer. Note the call to vkGetDeviceQueue(). This Vulkan function fetches the handle of a VkQueue object from the device. The first three parameters are the device, the index of the queue family and the index of the queue in that queue family (zero in our case because there is only one queue). The driver returns the result in the last parameter. The two getter functions here were added in this tutorial to the Vulkan core object.

相似上一篇教程中的方式,這個函數開始時建立和初始化窗口控件和Vulkan核心對象。以後,咱們調用privaite成員,建立交換鏈和命令緩存,將清空指令寫入命令緩存。注意對vkGetDeviceQueue()的調用。這個Vulkan函數從device提取VkQueue對象的句柄。前3個參數分別是device、queue family的索引和queue在queue family中的索引(本例中爲0,由於只有1個queue)。驅動返回的結果保存到最後的參數裏。本教程還加入了兩個對Vulkan核心對象的getter函數。

Let's review the creation of the swap chain step by step:

咱們來一步步地評審建立交換鏈的過程:

1 void OgldevVulkanApp::CreateSwapChain() 2 { 3     const VkSurfaceCapabilitiesKHR& SurfaceCaps = m_core.GetSurfaceCaps(); 4          
5     assert(SurfaceCaps.currentExtent.width != -1);

 

The first thing we need to do is to fetch the surface capabilities from the Vulkan core object. Remember that in the previous tutorial we populated a physical device database in the Vulkan core object with info about all the physical devices in the system. Some of that info was not generic but specific to the combination of the physical device and the surface that was created earlier. An example is the VkSurfaceCapabilitiesKHR vector which contains a VkSurfaceCapabilitiesKHR structure for each physical device. The function GetSurfaceCaps() indexes into that vector using the physical device index (which was selected in the previous tutorial). The VkSurfaceCapabilitiesKHR structure contains a lot of info on the surface. The currentExtent member describes the current size of the surface. Its type is a VkExtent2D which contains a width and height. Theoretically, the current extent should contain the dimensions that we have set when creating the surface and I have found that to be true on both Linux and Windows. In several examples (including the one in the Khronos SDK) I saw some logic which checks whether the width of the current extent is -1 and if so overwrites that with desired dimensions. I found that logic to be redundant so I just placed the assert you see above.

+BIT祝威+悄悄在此留下版了個權的信息說:

咱們要作的第一件事是從Vulkan核心對象獲取surface的capabilities。回憶上一篇教程中咱們在Vulkan核心對象中填入了一個physical device數據庫,其中含有系統上全部的physical device信息。有些信息不是通用的,而是針對以前建立的physical device和surface的組合的。一個粒子是VkSurfaceCapabilitiesKHR數組,其包含對每一個physical device的VkSurfaceCapabilitiesKHR結構體。函數用physical device索引(在上一篇教程中選擇的)使用這個數組。VkSurfaceCapabilitiesKHR結構體包含surface的不少信息。其中的currentExtent成員描述了surface當前的大小。它的類型是VkExtent2D,其包含寬度和高度。理論上,當建立surface時,當前範圍應該包含咱們設置的維度。我發現這在Linux和Windows上都是真的。在幾個例子中(包括Khronos SDK中的例子)我看到一些邏輯是用於檢查當前範圍的寬度是不是-1.若是是,就用須要的維度覆蓋那個寬度。我發現那個邏輯是多餘的,因此我就用上述代碼中的assert替換了它。

1     uint NumImages = 2; 2 
3     assert(NumImages >= SurfaceCaps.minImageCount); 4     assert(NumImages <= SurfaceCaps.maxImageCount);

 

Next we set the number of images that we will create in the swap chain to 2. This mimics the behavior of double buffering in OpenGL. I added assertions to make sure that this number is within the valid range of the platform. I assume that you won't hit these assertions but if you do you can try with one image only.

接下來,咱們將交換鏈中的image數量設置爲2。這模仿了OpenGL中的雙緩存。我加入了assert來確保這個數值是在平臺的有效要求內的。我假設你不會觸發這些assert,可是若是你碰到了,你能夠試試只有1個image。

1     VkSwapchainCreateInfoKHR SwapChainCreateInfo = {}; 2     
3     SwapChainCreateInfo.sType            = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; 4     SwapChainCreateInfo.surface          = m_core.GetSurface(); 5     SwapChainCreateInfo.minImageCount    = NumImages;

 

The function that creates the swap chain takes most of its parameters from the VkSwapchainCreateInfoKHR structure. The first three parameters are obvious - the structure type, the surface handle and the number of images. Once created the swap chain is permanently attached to the same surface.

建立交換鏈的函數的大部分參數來自VkSwapchainCreateInfoKHR結構體。前3個參數很明顯——結構體類型,surface句柄和image數量。一旦建立後,交換鏈就永遠附着到同一surface上了。

1     SwapChainCreateInfo.imageFormat      = m_core.GetSurfaceFormat().format; 2     SwapChainCreateInfo.imageColorSpace  = m_core.GetSurfaceFormat().colorSpace;

 

Next comes the image format and color space. The image format was discussed in the previous tutorial. It describes the layout of data in image memory. It contains stuff such as channels (red, green and/or blue) and format (float, normalized int, etc). The color space describes the way the values are matched to colors. For example, this can be linear or sRGB. We will take both from the physical device database.

+BIT祝威+悄悄在此留下版了個權的信息說:

接下來是image格式和顏色空間。上一篇教程討論過顏色格式了。它描述數據在image內存中的佈局方式。它包含通道(RGB)、格式(float,標準化int,等)等內容。顏色空間描述值映射到顏色的方式。例如,能夠是線性的或sRGB的。咱們將從physical device數據庫使用這兩種。

1     SwapChainCreateInfo.imageExtent      = SurfaceCaps.currentExtent;

 

We can create the swap chain with a different size than the surface. For now, just grab the current extent from the surface capabilities structure.

咱們能夠建立大小與surface不一樣的交換鏈。目前,就用surface的capabilities結構體的當前範圍好了。

1     SwapChainCreateInfo.imageUsage       = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

 

We need to tell the driver how we are going to use this swap chain. We do that by specifying a combination of bit masks and there are 8 usage bits in total. For example, the swap chain can be used as a source or destination of a transfer (buffer copy) operation, as a depth stencil attachment, etc. We just want a standard color buffer so we use the bit above.

咱們須要告訴驅動,咱們將如何使用交換鏈。咱們經過標識一個最多8位的掩碼來實現。例如,交換鏈能夠被用於轉移(緩存複製)的源或目的,用於模板附件,等。咱們只想要一個標準的顏色緩存,因此用上述位掩碼。

1     SwapChainCreateInfo.preTransform     = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;

 

The pre transform field was designed for hand held devices that can change their orientation (cellular phones and tablets). It specifies how the orientation must be changed before presentation (90 degrees, 180 degrees, etc). It is more relevant to Android so we just tell the driver not to do any orientation change.

字段preTransform用於可改變朝向的手持設備(移動電話和平板電腦)。它標明在顯示前應該如何改變朝向(90度,180度,等)。這和Android關係比較大,因此咱們告訴驅動不修改朝向。

1     SwapChainCreateInfo.imageArrayLayers = 1;

 

imageArrayLayers is intended for stereoscopic applications where rendering takes place from more than one location and then combined before presentations. An example is VR where you want to render the scene from each eye separately. We are not going to do that today so just specify 1.

字段imageArrayLayers用於立體應用程序,其渲染髮生在不止一處,而後聯合起來再顯示。咱們今天就不那麼作了,因此填上1便可。

1     SwapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;

 

Swap chain images can be shared by queues of different families. We will use exclusive access by the queue family we have selected previously.

+BIT祝威+悄悄在此留下版了個權的信息說:

交換鏈的image能夠被不一樣family的queue共享。咱們將??(譯者注:看不懂)

1     SwapChainCreateInfo.presentMode      = VK_PRESENT_MODE_FIFO_KHR;

 

In the previous tutorial we briefly touched on the presentation engine which is the part of the platform involved in actually taking the swap chain image and putting it on the screen. This engine also exists in OpenGL where it is quite limited in comparison to Vulkan. In OpenGL you can select between single and double buffering. Double buffering avoids tearing by switching the buffers only on VSync and you have some control on the number of VSync in a second. That's it. Vulkan, however, provides you with no less than four different modes of operation that allow a higher level of flexibility and performance. We will be conservative here and use the FIFO mode which is the most similar to OpenGL double buffering.

上一篇教程中咱們稍微說起了表現引擎,它參與了接收交換鏈image並將其放到屏幕上的過程。這個引擎也在OpenGL中存在,可是與Vulkan相比,存在感很低。在OpenGL中你能夠在單緩存和雙緩存中選擇。雙緩存避免了切換緩存時的撕裂,你還能夠控制垂直同步的速度。僅此而已。可是,Vulkan提供至少4種操做模式,容許更高的擴展性和性能。咱們就保守點,用FIFO模式,這是最接近OpenGL雙緩存的模式。

1     SwapChainCreateInfo.clipped          = true;

 

The clipped field indicates whether the driver can discard parts of the image that are outside of the visible surface. There are some obscure cases where this is interesting but not in our case.

字段clipped表面驅動十分能忽略image位於可見surface的外部的部分。有時候這會有稀裏糊塗的問題,可是咱們的例子裏沒有。

1     SwapChainCreateInfo.compositeAlpha   = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;

 

compositeAlpha controls the manner in which the image is combined with other surfaces. This is only relevant on some of the operating systems so we don't use it.

字段控制image聯合其餘surface的方式。這隻在某些操做系統的纔有用,咱們不用管它。

1     VkResult res = vkCreateSwapchainKHR(m_core.GetDevice(), &SwapChainCreateInfo, NULL, &m_swapChainKHR); 2     CHECK_VULKAN_ERROR("vkCreateSwapchainKHR error %d\n", res);

 

Finally, we can create the swap chain and get its handle.

+BIT祝威+悄悄在此留下版了個權的信息說:

最後,咱們建立交換鏈,獲得它的句柄。

1     uint NumSwapChainImages = 0; 2     res = vkGetSwapchainImagesKHR(m_core.GetDevice(), m_swapChainKHR, &NumSwapChainImages, NULL); 3     CHECK_VULKAN_ERROR("vkGetSwapchainImagesKHR error %d\n", res);

 

When we created the swap chain we specified the minimum number of images it should contain. In the above call we fetch the actual number of images that were created.

建立交換鏈後,咱們標明瞭它應該包含的image的最小數量。上述代碼中咱們獲取了實際建立的image數量。

1  m_images.resize(NumSwapChainImages); 2  m_cmdBufs.resize(NumSwapChainImages); 3     
4     res = vkGetSwapchainImagesKHR(m_core.GetDevice(), m_swapChainKHR, &NumSwapChainImages, &(m_images[0])); 5     CHECK_VULKAN_ERROR("vkGetSwapchainImagesKHR error %d\n", res); 6 }

 

We have to get the handles of all the swap chain images so we resize the image handle vector accordingly. We also resize the command buffer vector because we will record a dedicated command buffer for each image in the swap chain.

咱們必須獲得全部交換鏈image的句柄,因此咱們調整句柄數組的大小。咱們還要調整命令緩存數組的大小,由於咱們將爲交換鏈的每一個image記錄一個命令緩存。

The following function creates the command buffers:

下述函數建立了命令緩存:

1 void OgldevVulkanApp::CreateCommandBuffer() 2 { 3     VkCommandPoolCreateInfo cmdPoolCreateInfo = {}; 4     cmdPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 5     cmdPoolCreateInfo.queueFamilyIndex = m_core.GetQueueFamily(); 6     
7     VkResult res = vkCreateCommandPool(m_core.GetDevice(), &cmdPoolCreateInfo, NULL, &m_cmdBufPool); 8     CHECK_VULKAN_ERROR("vkCreateCommandPool error %d\n", res);

 

Command buffer are not created directly. Instead, they must be allocated from pools. As expected, the motivation is performance. By making command buffers part of a pool, better memory management and reuse can be implemented. It is imported to note that the pools are not thread safe. This means that any action on the pool or its command buffers must be explicitly synchronized by the application. So if you want multiple threads to create command buffers in parallel you can either do this synchronization or simply create a different pool for each thread.

命令緩存不是直接建立的。相反,它們必須從池裏分配。能夠想見,動機是性能。讓命令緩存稱爲池的一部分,能夠實現更好的內存管理和複用。重要的一點是,池不是線程安全的。這意味着對池或它的命令緩存的操做必須是明確的同步執行。因此若是你想在多線程並行地建立命令緩存,要麼同步執行,要麼爲不一樣的線程各建立一個線程。

The function vkCreateCommandPool() creates the pool. It takes a VkCommandPoolCreateInfo structure parameter whose most important member is the queue family index. All commands allocated from this pool must be submitted to queues from this queue family.

函數vkCreateCommandPool()建立這個池。它接收VkCommandPoolCreateInfo結構體做爲參數,其最重要的成員是quue family索引。由此池申請的全部命令都必須提交到這個queue family。

1     VkCommandBufferAllocateInfo cmdBufAllocInfo = {}; 2     cmdBufAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 3     cmdBufAllocInfo.commandPool = m_cmdBufPool; 4     cmdBufAllocInfo.commandBufferCount = m_images.size(); 5     cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 6         
7     res = vkAllocateCommandBuffers(m_core.GetDevice(), &cmdBufAllocInfo, &m_cmdBufs[0]); 8     CHECK_VULKAN_ERROR("vkAllocateCommandBuffers error %d\n", res); 9 }

 

We are now ready to create the command buffers. In the VkCommandBufferAllocateInfo structure we specify the pool we have just created and the number of command buffers (we need a dedicated command buffer per image in the swap chain). We also specify whether this is a primary or secondary command buffer. Primary command buffers are the common vehicle for submitting commands to the GPU but they cannot reference each other. This means that you can have two very similar command buffers but you still need to record everything into each one. You cannot share the common stuff between them. This is where secondary command buffers come in. They cannot be directly submitted to the queues but they can be referenced by primary command buffers which solves the problem of sharing. At this point we only need primary command buffers.

咱們如今能夠建立命令緩存了。在VkCommandBufferAllocateInfo結構體中咱們標明瞭咱們剛剛建立的池和命令緩存的數量(對交換鏈中的每一個image,咱們須要一個專用的命令緩存)。一級命令緩存是提交命令到GPU的輪子,可是它們不能相互引用。這意味着你可能有兩個很類似的命令緩存,可是你仍是須要在每一個裏記錄全部的信息。你不能在二者之間共享任何東西。這就是二級命令緩存出場的時候了。它們不能被直接提交到queue,可是能夠被一級命令緩存引用,這解決了共享的問題。目前咱們只須要一級命令緩存。

Now let's record the clear instruction into our new command buffers.

如今咱們將清空指令寫入咱們新的命令緩存中。

1 void OgldevVulkanApp::RecordCommandBuffers() 2 { 3     VkCommandBufferBeginInfo beginInfo = {}; 4     beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 5     beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;

 

Recording of command buffers must be done inside a region of the code explictly marked by a vkBeginCommandBuffer() and vkEndCommandBuffer(). In the VkCommandBufferBeginInfo structure we have a field named 'flags' where we tell the driver that the command buffers will be resubmitted to the queue over and over again. There are other usage models but for now we don't need them.

+BIT祝威+悄悄在此留下版了個權的信息說:

記錄命令緩存必須在代碼vkBeginCommandBuffer()和vkEndCommandBuffer()之間進行。在結構體中咱們有一個字段'flags',它告訴驅動命令緩存將會被反覆提交到queue。還有其餘使用模式,不過暫時咱們不須要。

1     VkClearColorValue clearColor = { 164.0f/256.0f, 30.0f/256.0f, 34.0f/256.0f, 0.0f }; 2     VkClearValue clearValue = {}; 3     clearValue.color = clearColor;

 

We have to specify our clear color using the two structures above. The first one is a union of four float/int/uint which allows different ways to do that. The second structure is a union of a VkClearColorValue structure and a VkClearDepthStencilValue structure. This scheme is used in parts of the API that can take either of the two structures. We go with the color case. Since I'm very creative today I used the RGB values from the color of the Vulkan logo ;-) 

咱們必須用上述2個結構體聲明本身的清空顏色。第一個是4個float/int/uint的聯合體,支持多種使用方式。第二個是VkClearColorValue結構體和VkClearDepthStencilValue結構體的聯合體。這個方案廣泛運用於能接收兩種結構體的API。咱們用顏色功能。因爲我今天創造力十足,我用的RGB值來自Vulkan的logo顏色。嘿嘿。

Note that each color channel goes from 0 (darkest) to 1 (brightest) and that this endless spectrum of real numbers is divided to 256 discrete segments which is why I divide by 256.

注意,每一個顏色通道都是從0(最暗)到1(最亮),這個無限的實數光譜被分爲256個離散的片斷。所以我這裏除了256。

1     VkImageSubresourceRange imageRange = {}; 2     imageRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 3     imageRange.levelCount = 1; 4     imageRange.layerCount = 1;

 

We need to specify the range of images that we want to clear. In future tutorials we will study more complex schemes where there will be multiple mipmap levels, layers, etc. For now we just want the basics so we specify one mip map level and one layer. The aspectMask field tells the driver whether to clear the color, depth or stenctil (or a combination of them). We are only interested in the color aspect of the images.

咱們要標明須要清空的image。在將來的教程中,咱們將要就更復雜的方案,到時候會有多mipmap level,層,等。目前咱們只想作基本工做,因此咱們標識1個mipmap level和1個層便可。字段aspectMask告訴驅動是否要清空顏色、深度或模版(或其聯合體)。咱們只對image的顏色方面感興趣。

 1     for (uint i = 0 ; i < m_cmdBufs.size() ; i++) {  2         VkResult res = vkBeginCommandBuffer(m_cmdBufs[i], &beginInfo);  3         CHECK_VULKAN_ERROR("vkBeginCommandBuffer error %d\n", res);  4 
 5         vkCmdClearColorImage(m_cmdBufs[i], m_images[i], VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &imageRange);  6 
 7         res = vkEndCommandBuffer(m_cmdBufs[i]);  8         CHECK_VULKAN_ERROR("vkEndCommandBuffer error %d\n", res);  9  } 10 }

 

We are now ready to record the command buffers. As mentioned earlier, the commands that do the actual recording must be inside a block marked by calls that begin and end a command buffer. For that we specify the command buffer to record to and the beginInfo structure which we already prepared. Since we have an array of command buffers (one buffer per swap chain image) the entire thing is enclosed inside a for loop. vkCmdClearColorImage() records the clear instruction into the command buffer. As parameters it takes the command buffer to record, the target image, the layout of the image in memory, the clear color, the number of VkImageSubresourceRange structures to use and a pointer to an array of these structures (only one in our case).

咱們能夠開始記錄命令緩存了。以前提到過,記錄這些命令的操做必須位於開始和結束命令緩存的函數調用之間。爲此,咱們標明須要記錄的命令緩存和備好的beginInfo結構體。因爲咱們有命令緩存的數組(每一個交換鏈image對應一個緩存),整件事被包在一個for循環裏。函數vkCmdClearColorImage()記錄清空指令到命令緩存中。它接收命令緩存、目標image、image在內存中的佈局、清空色、VkImageSubresourceRange結構體的數量和指向這些結構體數組的指針爲參數,

We prepared everything we need and we can now code our main render function. In standard OpenGL this usually means specifying a list of GL commands to draw stuff followed by a swap buffers call (be it GLUT, GLFW or any other windowing API). For the driver it means a tedious repetition of command buffer recording and submission where changes from one frame to the next are relatively small (changes in shader matrices, etc). But in Vulkan all our command buffers are already recorded! We just need to queue them to the GPU. Since we have to be more verbose in Vulkan we also need to manage how we acquire and image for rendering and how to tell the presentation image to display it.

咱們準備好了所需的一切, 如今能夠編寫主渲染函數了。在標準OpenGL中這一般意味着標明不少GL命令,以渲染些什麼,以後再交換緩存(用GLUT、GLFW或任何其餘窗口API)。對於區域,這意味着一個冗長乏味的重複命令緩存記錄和提交操做,兩幀之間的變化其實很小(shader矩陣的改變,等)。可是在Vulkan中咱們全部的命令緩存都已經記錄好了!咱們只需將它們排隊送到GPU。咱們在Vulkan中不得不作不少冗長的工做,還須要管理如何請求要渲染的image,如何告訴表現image去顯示。

1 void OgldevVulkanApp::RenderScene() 2 { 3     uint ImageIndex = 0; 4     
5     VkResult res = vkAcquireNextImageKHR(m_core.GetDevice(), m_swapChainKHR, UINT64_MAX, NULL, NULL, &ImageIndex); 6     CHECK_VULKAN_ERROR("vkAcquireNextImageKHR error %d\n", res);

 

The first thing we need to do is to acquire an image from the presentation engine which is available for rendering. We can acquire more than one image (e.g. if we plan to render two or more frames ahead) in an advanced scenario but for now one image will be enough. The API call above takes the device and swap chain as the first two parameters, respectively. The third parameter is the amount of time we're prepared to wait until that function returns. Often, the presentation engine cannot provide an image immediately because it needs to wait for an image to be released or some internal OS or GPU event (e.g. the VSync signal of the display). If we specify zero we make this a non blocking call which means that if an image is available we get it immediately and if not the function returns with an error. Any value above zero and below the maximum value of an unsigned 64bit integer will cause a timeout of that number of nanoseconds. The value of UINT64_MAX will cause the function to return only when an image becomes available (or some internal error occured). This seems like the safest course of action for us here. The next two parameters are pointers to a semaphore and a fence, respectively. Vulkan was designed with a lot of asynchronous operation in mind. This means that you can define inter-dependencies between queues on the GPU, between the CPU and GPU, etc. This allows you to submit work to the image even if it is not really ready to be rendered to (which is a bit counter intuitive to what vkAcquireNextImageKHR is supposed to do but can still happen). These semaphore and fence are synchornization primitives that must be waited upon before the actual rendering to the image can begin. A semaphore syncs between stuff on the GPU and the fence between the host CPU and the GPU. As you can see, I've specified NULL in both cases which might be unsafe and theoretically is not supposed to work yet it does. This may be because of the simplicity of our application. It allowed me to postpone all the synchronization business to a later date. Please let me know if you encounter problems because of this. The last parameter to the function is the index of the image that became available.

+BIT祝威+悄悄在此留下版了個權的信息說:

咱們須要作的第一件事,是從表現引擎中獲取一個可用於渲染的image。在高級場景中,咱們能夠獲取不止一個(例如,若是咱們計劃提早渲染2個或多個幀),但目前1個image就足夠了。上述API調用接收device和交換鏈爲前2個參數。第3個參數是咱們準備等待函數返回的時間。經常地,表現引擎不能當即提供image,由於他須要等待image被釋放或某些操做系統內部或GPU事件(例如顯示的垂直同步信號)。若是咱們寫0,咱們就讓它成爲了一個非阻塞調用,也就是說,若是有image可用,咱們會當即獲得它,若是沒有,函數就返回一個error。任何大於0小於uint64的整數都會引起超時(納秒)。UINT64_MAX的值會讓函數只在有可用image(或者發生內部錯誤)時才返回。這看起來像是最安全的選擇。後2個參數是信號和fence指針。Vulkan被設計爲不少異步操做。這意味着你能夠在GPU上的queue之間、在CPU和GPU之間定義相互依賴關係。這運行你提交工做到image,即便它尚未準備好被渲染(這違反直覺,vkAcquireNextImageKHR本來不應這樣,可是仍舊是可能發生的)。這些信號和fence是同步的基石,實際渲染到image開始前,必須等它們。一個信號同步GPU上的東西,fence用於宿主CPU和GPU之間。如你所見,我用NULL填入參數,這可能不安全,理論上行不通,但實際上仍是工做了。

1     VkSubmitInfo submitInfo = {}; 2     submitInfo.sType                = VK_STRUCTURE_TYPE_SUBMIT_INFO; 3     submitInfo.commandBufferCount   = 1; 4     submitInfo.pCommandBuffers      = &m_cmdBufs[ImageIndex]; 5     
6     res = vkQueueSubmit(m_queue, 1, &submitInfo, NULL); 7     CHECK_VULKAN_ERROR("vkQueueSubmit error %d\n", res);

 

Now that we have an image, let's submit the work to the queue. The vkQueueSubmit() function takes the handle of a queue, the number of VkSubmitInfo structures and a pointer to the corresponding array. The last parameter is a fence which we will conviniently ignore for now. The VkSubmitInfo actually contains 8 members in addition to the standard sType, but we are going to use only 2 (so just imagine how much complexity is still down there). We specify that we have one command buffer and we provide its address (the one that corresponds to the acquired image). The Vulkan spec notes that submission of work can have a high overhead and encourages us to pack as many command buffers as we possibly can into that API to minimize that overhead. In this simple example we don't have an opportunity to do that but we should keep that in mind as our application becomes more complex in the future.

如今咱們有了image,咱們把工做提交到queue吧。函數vkQueueSubmit()接收queue的句柄,結構體的數量和對於數組的指針。最後一個參數是fence,目前咱們忽略它。除了sType外,VkSubmitInfo實際上還有8個成員,可是咱們計劃只用2個(因此想象下後面還會有多少複雜的東西吧)。咱們標明咱們還有1個命令緩存,提供它的地址(對應到獲取到的image的那個)。Vulkan說明書提到,提交工做的開銷比較大,鼓勵咱們儘量打包最多的命令緩存到API,以最小化開銷。在這個簡單的例子中,咱們沒有機會這麼作,可是咱們應該記住這一點,由於應用程序會變得愈來愈複雜。

1     VkPresentInfoKHR presentInfo = {}; 2     presentInfo.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; 3     presentInfo.swapchainCount     = 1; 4     presentInfo.pSwapchains        = &m_swapChainKHR; 5     presentInfo.pImageIndices      = &ImageIndex; 6     
7     res = vkQueuePresentKHR(m_queue, &presentInfo); 8     CHECK_VULKAN_ERROR("vkQueuePresentKHR error %d\n" , res); 9 }
+BIT祝威+悄悄在此留下版了個權的信息說:

 

Once the previous API call has returned we know that the command buffer is on its way to the GPU queue but we have no idea when exactly it is going to be executed, and frankly, we don't really care. Command buffers in a queue are guaranteed to be processed in the order of submission and since we submit a present command after the clear command into the same queue we know that the image will be cleared before it is presented. So the vkQueuePresent() call is basically a marker that ends the frame and tells the presentation engine to display it. This function takes two parameters - a queue which has presentation capabilities (we took care of that when initializing the device and queue) and a pointer to a VkPresentInfoKHR structure. This structure contains, among other stuff, two arrays of equal sizes. A swap chain array and an image index array. This means that you can queue a present command to multiple swap chains where each swap chain is connected to a different window. Every swap chain in the array has a corresponding image index which specifies which image will be presented. The swapchainCount member says how many swap chains and images we are going present.

一旦以前的API返回了,咱們就知道命令緩存前往GPU的queue了,可是咱們不知道具體什麼時候它纔會被執行,坦白說,咱們也不在意。一個queue裏的命令緩存被保證會按提交的順序執行,因爲咱們在清空命令以後向同一queue提交顯示命令,咱們知道image會先清空後顯示。因此調用vkQueuePresent()函數基本上就是標記幀結束,告訴表現引擎去顯示。這個函數接收2個參數——有表現能力的queue(初始化device和queue的時候咱們處理好了它)和VkPresentInfoKHR結構體的指針。除了其餘東西,這個結構體還包含兩個大小相同的數組——一個交換鏈數組和一個image索引數組。這意味着你能夠將一個顯示命令排到多個交換鏈的queue上,每一個交換鏈均可以鏈接到不一樣的窗口。數組中的每一個交換鏈有個對應的image索引,標明哪一個image要被顯示。成員swapchainCount告訴咱們咱們要顯示多少交換鏈和image。

1 void OgldevVulkanApp::Run() 2 { 3     while (true) { 4  RenderScene(); 5  } 6 }

 

Our main render function is very simple. We loop endlessly and call the function that we have just reviewed.

+BIT祝威+悄悄在此留下版了個權的信息說:

咱們的主渲染函數很簡單。咱們無限循環,調用剛剛評審過的函數便可。

 1 int main(int argc, char** argv)  2 {  3     OgldevVulkanApp app("Tutorial 51");  4     
 5  app.Init();  6     
 7  app.Run();  8     
 9     return 0; 10 }

 

The main function is also very simple. We declare an OgldevVulkanApp object, initialize and run it.

+BIT祝威+悄悄在此留下版了個權的信息說:

主函數main仍是很簡單。咱們聲明OgldevVulkanApp對象,初始化和運行它。

That's it for today. I hope that your window is clear. Next time we will draw a triangle.

今天就到這裏吧。我行爲你的窗口被清空了。下次咱們將畫一個三角形。

 

原文出處:https://www.cnblogs.com/bitzhuwei/p/csharpgl-57-Vulkan-Clear-Screen.html

相關文章
相關標籤/搜索