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