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 }
實現和解釋起來略顯復雜,但使用起來十分簡單:
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用於指定背景色,這里指定了黃色,運行效果如下:
總結
如果看不懂本文,就去看代碼,運行代碼,再來看本文。反反復復看,總會懂。