1. 程式人生 > >Vulkan(0)搭建環境-清空視窗

Vulkan(0)搭建環境-清空視窗

Vulkan(0)搭建環境-清空視窗

認識Vulkan

Vulkan是新一代3D圖形API,它繼承了OpenGL的優點,彌補了OpenGL的缺憾。有點像科創板之於主機板,殲20之於殲10,微信之於QQ,網店之於實體店,今日之於昨日。

使用OpenGL時,每次drawcall都需要向OpenGL提交很多資料。而Vulkan可以提前將這些drawcall指令儲存到一個buffer(像儲存頂點資料到buffer一樣),這樣就減少了很多開銷。

使用OpenGL時,OpenGL的Context會包含很多你並不打算使用的東西,例如線的寬度、混合等。而Vulkan不會提供這些你用不到的東西,你需要什麼,你來指定。(當然,你不指定,Vulkan不會自動地提供)

Vulkan還支援多執行緒,OpenGL這方面就不行了。

Vulkan對GPU的抽象比OpenGL更加細膩。

搭建環境

本文和本系列都將使用C#和Visual Studio 2017來學習使用Vulkan。

首先,在官網(https://vulkan.lunarg.com)下載vulkan-sdk.exe和vulkan-runtime.exe。完後安裝。vulkan-runtime.exe也可以在(https://files.cnblogs.com/files/bitzhuwei/vulkan-runtime.rar)下載。vulkan-sdk.exe太大,我就不提供下載了。

然後,下載Vulkan.net庫(https://github.com/bitzhuwei/Vulkan.net)。這是本人蒐羅整理來的一個Vulkan庫,外加一些示例程式碼。用VS2017開啟Vulkan.sln,在這個解決方案下就可以學習使用Vulkan了。

如果讀者在Github上的下載速度太慢,可以試試將各個檔案單獨點開下載。這很笨,但也是個辦法。

簡單介紹下此解決方案。

Vulkan資料夾下的Vulkan.csproj是對Vulkan API的封裝。Vulkan使用了大量的struct、enum,這與OpenGL類似。

Vulkan.Platforms資料夾下的Vulkan.Platforms.csproj是平臺相關的一些API。

Lesson01Clear資料夾下的是第一個示例,展示了Vulkan清空視窗的程式碼。以後會逐步新增更多的示例。

有了這個庫,讀者就可以執行示例程式,一點點地讀程式碼,慢慢理解Vulkan了。這也是本人用的最多的學習方法。遇到不懂的就上網搜尋,畢竟我沒有別人可以問。

這個庫還很不成熟,以後會有大的改動。但這不妨礙學習,反而是學習的好資料,在變動的過程中方能體會軟體工程的精髓。

清空視窗

用Vulkan寫個清空視窗的程式,就像是用C寫個hello world。

外殼

新建Windows窗體應用程式。

 

 新增對類庫Vulkan和Vulkan.Platforms的引用:

 

 新增此專案的核心型別LessonClear。Vulkan需要初始化(Init)一些東西,在每次渲染時,渲染(Render)一些東西。

 1 namespace Lesson01Clear {
 2     unsafe class LessonClear {
 3     
 4         bool isInitialized = false;
 5 
 6         public void Init() {
 7             if (this.isInitialized) { return; }
 8 
 9             this.isInitialized = true;
10         }
11 
12         public void Render() {
13             if (!isInitialized) return;
14 
15         }
16     }
17 }

 

新增一個User Control,用以呼叫LessonClear。

 1 namespace Lesson01Clear {
 2     public partial class UCClear : UserControl {
 3 
 4         LessonClear lesson;
 5 
 6         public UCClear() {
 7             InitializeComponent();
 8         }
 9 
10         protected override void OnLoad(EventArgs e) {
11             base.OnLoad(e);
12 
13             this.lesson = new LessonClear();
14             this.lesson.Init();
15         }
16 
17         protected override void OnPaintBackground(PaintEventArgs e) {
18             var lesson = this.lesson;
19             if (lesson != null) {
20                 lesson.Render();
21             }
22         }
23     }
24 }

 

在主視窗中新增一個自定義控制元件UCClear。這樣,在視窗啟動時,就會自動執行LessonClear的初始化和渲染功能了。

 

 此時的解決方案如下:

 

 

初始化

要初始化的東西比較多,我們一項一項來看。

VkInstance

在LessonClear中新增成員變數VkInstance vkIntance,在InitInstance()函式中初始化它。

 1     unsafe class LessonClear {
 2         VkInstance vkIntance;
 3         bool isInitialized = false;
 4 
 5         public void Init() {
 6             if (this.isInitialized) { return; }
 7 
 8             this.vkIntance = InitInstance();
 9 
10             this.isInitialized = true;
11         }
12 
13         private VkInstance InitInstance() {
14             VkLayerProperties[] layerProperties;
15             Layer.EnumerateInstanceLayerProperties(out layerProperties);
16             string[] layersToEnable = layerProperties.Any(l => StringHelper.ToStringAnsi(l.LayerName) == "VK_LAYER_LUNARG_standard_validation")
17                 ? new[] { "VK_LAYER_LUNARG_standard_validation" }
18                 : new string[0];
19 
20             var appInfo = new VkApplicationInfo();
21             {
22                 appInfo.SType = VkStructureType.ApplicationInfo;
23                 uint version = Vulkan.Version.Make(1, 0, 0);
24                 appInfo.ApiVersion = version;
25             }
26 
27             var extensions = new string[] { "VK_KHR_surface", "VK_KHR_win32_surface", "VK_EXT_debug_report" };
28 
29             var info = new VkInstanceCreateInfo();
30             {
31                 info.SType = VkStructureType.InstanceCreateInfo;
32                 extensions.Set(ref info.EnabledExtensionNames, ref info.EnabledExtensionCount);
33                 layersToEnable.Set(ref info.EnabledLayerNames, ref info.EnabledLayerCount);
34                 info.ApplicationInfo = (IntPtr)(&appInfo);
35             }
36 
37             VkInstance result;
38             VkInstance.Create(ref info, null, out result).Check();
39 
40             return result;
41         }
42     }

 VkInstance的extension和layer是什麼,一時難以說清,先不管。VkInstance像是一個快取,它根據使用者提供的引數,準備好了使用者可能要用的東西。在建立VkInstance時,我明顯感到程式卡頓了1秒。如果使用者稍後請求的東西在快取中,VkInstance就立即提供給他;如果不在,VkInstance就不給,並丟擲VkResult。

以“Vk”開頭的一般是Vulkan的結構體,或者對某種Vulkan物件的封裝。

VkInstance就是一個對Vulkan物件的封裝。建立一個VkInstance物件時,Vulkan的API只會返回一個 IntPtr 指標。在本庫中,用一個class VkInstance將其封裝起來,以便使用。

建立一個VkInstance物件時,需要我們提供給Vulkan API一個對應的 VkInstanceCreateInfo 結構體。這個結構體包含了建立VkInstance所需的各種資訊,例如我們想讓這個VkInstance支援哪些extension、哪些layer等。對於extension,顯然,這必須用一個數組指標IntPtr和extension的總數來描述。

 1     public struct VkInstanceCreateInfo {
 2         public VkStructureType SType;
 3         public IntPtr Next;
 4         public UInt32 Flags;
 5         public IntPtr ApplicationInfo;
 6         public UInt32 EnabledLayerCount;
 7         public IntPtr EnabledLayerNames;
 8         public UInt32 EnabledExtensionCount; // 陣列元素的數量
 9         public IntPtr EnabledExtensionNames; // 陣列指標
10     }

 

這樣的情況在Vulkan十分普遍,所以本庫提供一個擴充套件方法來執行這一操作:

 1     /// <summary>
 2     /// Set an array of structs to specified <paramref name="target"/> and <paramref name="count"/>.
 3     /// <para>Enumeration types are not allowed to use this method.
 4     /// If you have to, convert them to byte/short/ushort/int/uint according to their underlying types first.</para>
 5     /// </summary>
 6     /// <param name="value"></param>
 7     /// <param name="target">address of first element/array.</param>
 8     /// <param name="count">How many elements?</param>
 9     public static void Set<T>(this T[] value, ref IntPtr target, ref UInt32 count) where T : struct {
10         {   // free unmanaged memory.
11             if (target != IntPtr.Zero) {
12                 Marshal.FreeHGlobal(target);
13                 target = IntPtr.Zero;
14                 count = 0;
15             }
16         }
17         {
18             count = (UInt32)value.Length;
19 
20             int elementSize = Marshal.SizeOf<T>();
21             int byteLength = (int)(count * elementSize);
22             IntPtr array = Marshal.AllocHGlobal(byteLength);
23             var dst = (byte*)array;
24             GCHandle pin = GCHandle.Alloc(value, GCHandleType.Pinned);
25             IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(value, 0);
26             var src = (byte*)address;
27             for (int i = 0; i < byteLength; i++) {
28                 dst[i] = src[i];
29             }
30             pin.Free();
31 
32             target = array;
33         }
34     }

 這個Set<T>()函式的核心作用是:在非託管記憶體上建立一個數組,將託管記憶體中的陣列T[] value中的資料複製過去,然後,記錄非託管記憶體中的陣列的首地址(target)和元素數量(count)。當然,如果這不是第一次讓target記錄非託管記憶體中的某個陣列,那就意味著首先應當將target指向的陣列釋放掉。

如果這裡的T是列舉型別, Marshal.SizeOf() 會丟擲異常,所以,必須先將列舉陣列轉換為 byte/short/ushort/int/uint 型別的陣列。至於Marshal.SizeOf為什麼會拋異常,我也不知道。

如果這裡的T是string,那麼必須用另一個變種函式代替:

 1     /// <summary>
 2     /// Set an array of strings to specified <paramref name="target"/> and <paramref name="count"/>.
 3     /// </summary>
 4     /// <param name="value"></param>
 5     /// <param name="target">address of first element/array.</param>
 6     /// <param name="count">How many elements?</param>
 7     public static void Set(this string[] value, ref IntPtr target, ref UInt32 count) {
 8         {   // free unmanaged memory.
 9             var pointer = (IntPtr*)(target.ToPointer());
10             if (pointer != null) {
11                 for (int i = 0; i < count; i++) {
12                     Marshal.FreeHGlobal(pointer[i]);
13                 }
14             }
15         }
16         {
17             int length = value.Length;
18             if (length > 0) {
19                 int elementSize = Marshal.SizeOf(typeof(IntPtr));
20                 int byteLength = (int)(length * elementSize);
21                 IntPtr array = Marshal.AllocHGlobal(byteLength);
22                 IntPtr* pointer = (IntPtr*)array.ToPointer();
23                 for (int i = 0; i < length; i++) {
24                     IntPtr str = Marshal.StringToHGlobalAnsi(value[i]);
25                     pointer[i] = str;
26                 }
27                 target = array;
28             }
29             count = (UInt32)length;
30         }
31     }
public static void Set(this string[] value, ref IntPtr target, ref UInt32 count)

 

實現和解釋起來略顯複雜,但使用起來十分簡單:

1 var extensions = new string[] { "VK_KHR_surface", "VK_KHR_win32_surface", "VK_EXT_debug_report" };
2 extensions.Set(ref info.EnabledExtensionNames, ref info.EnabledExtensionCount);
3 var layersToEnable = new[] { "VK_LAYER_LUNARG_standard_validation" };
4 layersToEnable.Set(ref info.EnabledLayerNames, ref info.EnabledLayerCount);

 在後續建立其他Vulkan物件時,我們將多次使用這一方法。

建立VkInstance的內部過程,就是呼叫Vulkan API的問題:

 1 namespace Vulkan {
 2     public unsafe partial class VkInstance : IDisposable {
 3         public readonly IntPtr handle;
 4         private readonly UnmanagedArray<VkAllocationCallbacks> callbacks;
 5 
 6         public static VkResult Create(ref VkInstanceCreateInfo createInfo, UnmanagedArray<VkAllocationCallbacks> callbacks, out VkInstance instance) {
 7             VkResult result = VkResult.Success;
 8             var handle = new IntPtr();
 9             VkAllocationCallbacks* pAllocator = callbacks != null ? (VkAllocationCallbacks*)callbacks.header : null;
10             fixed (VkInstanceCreateInfo* pCreateInfo = &createInfo) {
11                 vkAPI.vkCreateInstance(pCreateInfo, pAllocator, &handle).Check();
12             }
13 
14             instance = new VkInstance(callbacks, handle);
15 
16             return result;
17         }
18 
19         private VkInstance(UnmanagedArray<VkAllocationCallbacks> callbacks, IntPtr handle) {
20             this.callbacks = callbacks;
21             this.handle = handle;
22         }
23 
24         public void Dispose() {
25             VkAllocationCallbacks* pAllocator = callbacks != null ? (VkAllocationCallbacks*)callbacks.header : null;
26             vkAPI.vkDestroyInstance(this.handle, pAllocator);
27         }
28     }
29     
30     class vkAPI {
31         const string VulkanLibrary = "vulkan-1";
32 
33         [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
34         internal static unsafe extern VkResult vkCreateInstance(VkInstanceCreateInfo* pCreateInfo, VkAllocationCallbacks* pAllocator, IntPtr* pInstance);
35 
36         [DllImport(VulkanLibrary, CallingConvention = CallingConvention.Winapi)]
37         internal static unsafe extern void vkDestroyInstance(IntPtr instance, VkAllocationCallbacks* pAllocator);
38     }
39 }

在 public static VkResult Create(ref VkInstanceCreateInfo createInfo, UnmanagedArray<VkAllocationCallbacks> callbacks, out VkInstance instance); 函式中:

第一個引數用ref標記,是因為這樣就會強制程式設計師提供一個 VkInstanceCreateInfo 結構體。如果改用 VkInstanceCreateInfo* ,那麼程式設計師就有可能提供一個null指標,這對於Vulkan API的 vkCreateInstance() 是沒有應用意義的。

對第二個引數提供null指標是有應用意義的,但是,如果用 VkAllocationCallbacks* ,那麼此引數指向的物件仍舊可能位於託管記憶體中(從而,在後續階段,其位置有可能被GC改變)。用 UnmanagedArray<VkAllocationCallbacks> 就可以保證它位於非託管記憶體

對於第三個引數,之所以讓它用out標記(而不是放到返回值上),是因為 vkCreateInstance() 的返回值是 VkResult 。這樣寫,可以保持程式碼的風格與Vulkan一致。如果以後需要用切面程式設計之類的的方式新增log等功能,這樣的一致性就會帶來便利。

在函式中宣告的結構體變數(例如這裡的 var handle = new IntPtr(); ),可以直接取其地址( &handle )。

建立VkInstance的方式方法流程,與建立其他Vulkan物件的方式方法流程是極其相似的。讀者可以觸類旁通。

VkSurfaceKhr

在LessonClear中新增成員變數VkSurfaceKhr vkSurface,在InitSurface()函式中初始化它。

 1 namespace Lesson01Clear {
 2     unsafe class LessonClear {
 3         VkInstance vkIntance;
 4         VkSurfaceKhr vkSurface;
 5         bool isInitialized = false;
 6 
 7         public void Init(IntPtr hwnd, IntPtr processHandle) {
 8             if (this.isInitialized) { return; }
 9 
10             this.vkIntance = InitInstance();
11             this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
12             
13             this.isInitialized = true;
14         }
15 
16         private VkSurfaceKhr InitSurface(VkInstance instance, IntPtr hwnd, IntPtr processHandle) {
17             var info = new VkWin32SurfaceCreateInfoKhr {
18                 SType = VkStructureType.Win32SurfaceCreateInfoKhr,
19                 Hwnd = hwnd, // handle of User Control.
20                 Hinstance = processHandle, //Process.GetCurrentProcess().Handle
21             };
22             return instance.CreateWin32SurfaceKHR(ref info, null);
23         }
24     }
25 }

 可見,VkSurfaceKhr的建立與VkInstance遵循同樣的模式,只是CreateInfo內容比較少。VkSurfaceKhr需要知道視窗控制代碼和程序控制代碼,這樣它才能渲染到相應的視窗/控制元件上。

VkPhysicalDevice

這裡的物理裝置指的就是我們的計算機上的GPU了。

 1 namespace Lesson01Clear {
 2     unsafe class LessonClear {
 3         VkInstance vkIntance;
 4         VkSurfaceKhr vkSurface;
 5         VkPhysicalDevice vkPhysicalDevice;
 6         bool isInitialized = false;
 7 
 8         public void Init(IntPtr hwnd, IntPtr processHandle) {
 9             if (this.isInitialized) { return; }
10 
11             this.vkIntance = InitInstance();
12             this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
13             this.vkPhysicalDevice = InitPhysicalDevice();
14 
15             this.isInitialized = true;
16         }
17 
18         private VkPhysicalDevice InitPhysicalDevice() {
19             VkPhysicalDevice[] physicalDevices;
20             this.vkIntance.EnumeratePhysicalDevices(out physicalDevices);
21             return physicalDevices[0];
22         }
23     }
24 }

 

建立VkPhysicalDivice物件不需要Callback:

 1 namespace Vulkan {
 2     public unsafe partial class VkPhysicalDevice {
 3         public readonly IntPtr handle;
 4 
 5         public static VkResult Enumerate(VkInstance instance, out VkPhysicalDevice[] physicalDevices) {
 6             if (instance == null) { physicalDevices = null; return VkResult.Incomplete; }
 7 
 8             UInt32 count;
 9             VkResult result = vkAPI.vkEnumeratePhysicalDevices(instance.handle, &count, null).Check();
10             var handles = stackalloc IntPtr[(int)count];
11             if (count > 0) {
12                 result = vkAPI.vkEnumeratePhysicalDevices(instance.handle, &count, handles).Check();
13             }
14 
15             physicalDevices = new VkPhysicalDevice[count];
16             for (int i = 0; i < count; i++) {
17                 physicalDevices[i] = new VkPhysicalDevice(handles[i]);
18             }
19 
20             return result;
21         }
22 
23         private VkPhysicalDevice(IntPtr handle) {
24             this.handle = handle;
25         }
26     }
27 }

在函式中宣告的變數(例如這裡的 var handle = new IntPtr(); ),可以直接取其地址( &handle )。

但是在函式中宣告的陣列,陣列本身是在堆中的,不能直接取其地址。為了能夠取其地址,可以用( var handles = stackalloc IntPtr[(int)count]; )這樣的方式,這會將陣列本身建立到函式自己的棧空間,從而可以直接取其地址了。

VkDevice

這個裝置是對物理裝置的快取\抽象\介面,我們想使用物理裝置的哪些功能,就在CreateInfo中指定,然後建立VkDevice。(不指定的功能,以後就無法使用。)後續各種物件,都是用VkDevice建立的。

namespace Lesson01Clear {
    unsafe class LessonClear {
        VkInstance vkIntance;
        VkSurfaceKhr vkSurface;
        VkPhysicalDevice vkPhysicalDevice;
        VkDevice vkDevice;
        
        bool isInitialized = false;

        public void Init(IntPtr hwnd, IntPtr processHandle) {
            if (this.isInitialized) { return; }

            this.vkIntance = InitInstance();
            this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
            this.vkPhysicalDevice = InitPhysicalDevice();
            VkSurfaceFormatKhr surfaceFormat = SelectFormat(this.vkPhysicalDevice, this.vkSurface);
            VkSurfaceCapabilitiesKhr surfaceCapabilities;
            this.vkPhysicalDevice.GetSurfaceCapabilitiesKhr(this.vkSurface, out surfaceCapabilities);

            this.vkDevice = InitDevice(this.vkPhysicalDevice, this.vkSurface);

            this.isInitialized = true;
        }

        private VkDevice InitDevice(VkPhysicalDevice physicalDevice, VkSurfaceKhr surface) {
            VkQueueFamilyProperties[] properties = physicalDevice.GetQueueFamilyProperties();
            uint index;
            for (index = 0; index < properties.Length; ++index) {
                VkBool32 supported;
                physicalDevice.GetSurfaceSupportKhr(index, surface, out supported);
                if (!supported) { continue; }

                if (properties[index].QueueFlags.HasFlag(VkQueueFlags.QueueGraphics)) break;
            }

            var queueInfo = new VkDeviceQueueCreateInfo();
            {
                queueInfo.SType = VkStructureType.DeviceQueueCreateInfo;
                new float[] { 1.0f }.Set(ref queueInfo.QueuePriorities, ref queueInfo.QueueCount);
                queueInfo.QueueFamilyIndex = index;
            }

            var deviceInfo = new VkDeviceCreateInfo();
            {
                deviceInfo.SType = VkStructureType.DeviceCreateInfo;
                new string[] { "VK_KHR_swapchain" }.Set(ref deviceInfo.EnabledExtensionNames, ref deviceInfo.EnabledExtensionCount);
                new VkDeviceQueueCreateInfo[] { queueInfo }.Set(ref deviceInfo.QueueCreateInfos, ref deviceInfo.QueueCreateInfoCount);
            }

            VkDevice device;
            physicalDevice.CreateDevice(ref deviceInfo, null, out device);

            return device;
        }
    }
}

 後續的Queue、Swapchain、Image、RenderPass、Framebuffer、Fence和Semaphore等都不再一一介紹,畢竟都是十分類似的建立過程。

最後只介紹一下VkCommandBuffer。

VkCommandBuffer

Vulkan可以將很多渲染指令儲存到buffer,將buffer一次性上傳到GPU記憶體,這樣以後每次呼叫它即可,不必重複提交這些資料了。

 1 namespace Lesson01Clear {
 2     unsafe class LessonClear {
 3         VkInstance vkIntance;
 4         VkSurfaceKhr vkSurface;
 5         VkPhysicalDevice vkPhysicalDevice;
 6 
 7         VkDevice vkDevice;
 8         VkQueue vkQueue;
 9         VkSwapchainKhr vkSwapchain;
10         VkImage[] vkImages;
11         VkRenderPass vkRenderPass;
12         VkFramebuffer[] vkFramebuffers;
13         VkFence vkFence;
14         VkSemaphore vkSemaphore;
15         VkCommandBuffer[] vkCommandBuffers;
16         bool isInitialized = false;
17 
18         public void Init(IntPtr hwnd, IntPtr processHandle) {
19             if (this.isInitialized) { return; }
20 
21             this.vkIntance = InitInstance();
22             this.vkSurface = InitSurface(this.vkIntance, hwnd, processHandle);
23             this.vkPhysicalDevice = InitPhysicalDevice();
24             VkSurfaceFormatKhr surfaceFormat = SelectFormat(this.vkPhysicalDevice, this.vkSurface);
25             VkSurfaceCapabilitiesKhr surfaceCapabilities;
26             this.vkPhysicalDevice.GetSurfaceCapabilitiesKhr(this.vkSurface, out surfaceCapabilities);
27 
28             this.vkDevice = InitDevice(this.vkPhysicalDevice, this.vkSurface);
29 
30             this.vkQueue = this.vkDevice.GetDeviceQueue(0, 0);
31             this.vkSwapchain = CreateSwapchain(this.vkDevice, this.vkSurface, surfaceFormat, surfaceCapabilities);
32             this.vkImages = this.vkDevice.GetSwapchainImagesKHR(this.vkSwapchain);
33             this.vkRenderPass = CreateRenderPass(this.vkDevice, surfaceFormat);
34             this.vkFramebuffers = CreateFramebuffers(this.vkDevice, this.vkImages, surfaceFormat, this.vkRenderPass, surfaceCapabilities);
35 
36             var fenceInfo = new VkFenceCreateInfo() { SType = VkStructureType.FenceCreateInfo };
37             this.vkFence = this.vkDevice.CreateFence(ref fenceInfo);
38             var semaphoreInfo = new VkSemaphoreCreateInfo() { SType = VkStructureType.SemaphoreCreateInfo };
39             this.vkSemaphore = this.vkDevice.CreateSemaphore(ref semaphoreInfo);
40 
41             this.vkCommandBuffers = CreateCommandBuffers(this.vkDevice, this.vkImages, this.vkFramebuffers, this.vkRenderPass, surfaceCapabilities);
42 
43             this.isInitialized = true;
44         }
45 
46         VkCommandBuffer[] CreateCommandBuffers(VkDevice device, VkImage[] images, VkFramebuffer[] framebuffers, VkRenderPass renderPass, VkSurfaceCapabilitiesKhr surfaceCapabilities) {
47             var createPoolInfo = new VkCommandPoolCreateInfo {
48                 SType = VkStructureType.CommandPoolCreateInfo,
49                 Flags = VkCommandPoolCreateFlags.ResetCommandBuffer
50             };
51             var commandPool = device.CreateCommandPool(ref createPoolInfo);
52             var commandBufferAllocateInfo = new VkCommandBufferAllocateInfo {
53                 SType = VkStructureType.CommandBufferAllocateInfo,
54                 Level = VkCommandBufferLevel.Primary,
55                 CommandPool = commandPool.handle,
56                 CommandBufferCount = (uint)images.Length
57             };
58             VkCommandBuffer[] buffers = device.AllocateCommandBuffers(ref commandBufferAllocateInfo);
59             for (int i = 0; i < images.Length; i++) {
60 
61                 var commandBufferBeginInfo = new VkCommandBufferBeginInfo() {
62                     SType = VkStructureType.CommandBufferBeginInfo
63                 };
64                 buffers[i].Begin(ref commandBufferBeginInfo);
65                 {
66                     var renderPassBeginInfo = new VkRenderPassBeginInfo();
67                     {
68                         renderPassBeginInfo.SType = VkStructureType.RenderPassBeginInfo;
69                         renderPassBeginInfo.Framebuffer = framebuffers[i].handle;
70                         renderPassBeginInfo.RenderPass = renderPass.handle;
71                         new VkClearValue[] { new VkClearValue { Color = new VkClearColorValue(0.9f, 0.7f, 0.0f, 1.0f) } }.Set(ref renderPassBeginInfo.ClearValues, ref renderPassBeginInfo.ClearValueCount);
72                         renderPassBeginInfo.RenderArea = new VkRect2D {
73                             Extent = surfaceCapabilities.CurrentExtent
74                         };
75                     };
76                     buffers[i].CmdBeginRenderPass(ref renderPassBeginInfo, VkSubpassContents.Inline);
77                     {
78                         // nothing to do in this lesson.
79                     }
80                     buffers[i].CmdEndRenderPass();
81                 }
82                 buffers[i].End();
83             }
84             return buffers;
85         }
86     }
87 }

 本例中的VkClearValue用於指定背景色,這裡指定了黃色,執行效果如下:

 

 

總結

如果看不懂本文,就去看程式碼,執行程式碼,再來看本文。反反覆覆看,總會懂。

&n