Vulkan学习小结——框架搭建

来源:互联网 发布:金蝶软件多少钱一套 编辑:程序博客网 时间:2024/06/04 19:17

毕设要用Vulkan做个渲染模块,最近几天才开始学习....

一个典型的Vulkan程序框架如下所示:

class VulkanApplication{public:void run(){initWindow();initVulkan();mainLoop();}private:void initWindow(){glfwInit();glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);window = glfwCreateWindow(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_TITLE, nullptr, nullptr);}void initVulkan(){createInstance();setUpDebugCallback();createSurface();pickPhysicalDevice();createLogicalDevice();createSwapChain();createImageViews();createRenderPass();createGriphicsPipeline();createFragmentBuffer();createCommandPool();createCommandBuffer();createSemaphores();}void mainLoop(){while (!glfwWindowShouldClose(window)){glfwPollEvents();drawFrame();}vkDeviceWaitIdle(device);glfwDestroyWindow(window);glfwTerminate();}}
一.Vulkan创建流程的回顾:
1.使用GLFW初始化Window,设置ClientAPI类型,窗口是否可以拉伸,最后创建窗口。
2.初始化Vulkan部分:
(1)创建VkInstance
使用VkInstanceCreateInfo初始化信息,其包含VkApplicationInfo以及Extension的Validation Layer的信息。由于Vulkan是平台独立的API,与特定的窗口系统交互时需要引入额外的Extension,使用glfwGetReqiuredInstanceExtension获取窗口系统需要的Extension,使用vkEnumerateInstanceExtensionProperties获取当前版本Vulkan所支持的Extension。另外一个可选的属性就是Validation Layer,使用vkEnumerateInstanceLayerProperties检测当前Vulkan支持的Layer。
(2)设置调试信息
如果在上一步中开启了Debug使用的validationLayer,需要设置对应的回调,即创建vKDebugReportCallback的实例,对于每一个Vulkan对象,创建时都需要一个对应的CreateInfo,因此这里需要设置VkDebugReportCallbackCreateInfoEXT(扩展方法带后缀EXT),这里可以指定输出日志的级别,一般输出Warning和Error的信息。关于Validation Layer更详细的信息可以参考SDK目录下的vk_layer_settings.txt文件。
(3)创建Surface
Vulkan不能直接与窗口系统交互,需要使用WSI扩展来完成这一步骤。Surface对应Vulkan里的VkSurfaceKHR,glfw库提供了一个glfwCreateWindowSurface方法来屏蔽平台相关的操作(例如在Win32平台下需要获取窗口的HWND和HMODULE)。这一步是可选的,比如使用Vulkan做一些计算操作就不需要可视化的窗口。
(4)选择物理设备
这一步是选择一个支持Vulkan的显卡设备,使用vkEnumeratePhysicalDevices枚举出所有的显卡信息,随后检测每个设备是否支持应用所需要的Extension,QueueFamily以及SwapChain,如果要进行渲染操作,一般需要支持SwapChain。对每个筛选后的设备可以进行二次评分,最后选择最佳设备。
(5)创建逻辑设备
对于每个逻辑设备,需要创建其需要的Queue,如果要完成正常的渲染与显示,需要Graphics Queue和Present Queue。还需要指定使用的Extension以及Validation Layer的信息。在Device创建完成后,使用vkGetDeviceQueue取得对应Queue的引用。
(6)创建SwapChain
首先需要检查物理设备对SwapChain的支持信息,包括VkSurfaceCapabilitiesKHR,VkSurfaceFormatKHR和VkPresentModeKHR,接着根据物理设备对三者的支持程度选择最佳的一个,其中VkSurfaceCapabilitiesKHR对应VkExtent2D。在创建SwapChain之后,使用vkGetSwapchainImagesKHR获取其包含Image的引用。
(7)创建ImageView
为上一步的每个Image创建对应的VkImageView,通过ImageView来使用Image。
(8)创建RenderPass
RenderPass的创建需要Attachment(VkAttackmentDescription)以及SubPass(VkSubPassDescription)的信息,SubPass又需要VkSubpassDependency以及VkAttachmentReference的信息。SubPass在这里是必备的。即使并不使用SubPass。
(9)创建GraphicsPipeLine
首先创建VkShaderMoudle,Vulkan使用GLSL作为CG语言,但是不支持直接读取文本之后创建Shader对象,这是为了防止编译器错误导致程序崩溃,Vulkan使用的Shader在使用GLSL编写完成后,需要调用Shader的编译器将其编译成后缀为SPV的二进制文件,这一步可以检查出Shader的语言错误,确保在运行时Shader可以正确的运行。ShaderModule创建完成后,需要创建VkPipelineShaderStageCreateInfo来指定Shader的类型(顶点还是片元,入口函数)。
接着创建VkPipelineVertexInputStateCreateInfo,该对象配置了顶点的输入信息。
然后创建VkPipelineInputAssemblyStateCreateInfo,配置了图元的拓扑结构(点,线等)。
之后创建VkPipelineViewportStateCreateInfo,该配置需要VkViewport和裁剪区域VkRect2D的信息。
接下来是光栅化的配置VkPipelineRasterizationStateCreateInfo,多重采样的配置VkPipelineMultisampleStateCreateInfo以及颜色混合配置的VkPipelineColorBlendStateCreateInfo,动态配置VkPipelineDynamicStateCreateInfo和布局配置VkPipelineLayoutCreateInfo。
所有这些配置完成后,填充VkGraphicsPipelineCreateInfo结构并创建GraphicsPipeLine。
(10)创建FragmentBuffer
为每个VkImage创建对应的VkFragment对象,其attachments指向VkImageView。
(11)创建CommandPool
创建CommandPool来缓存Command,需要为CommandPool指定QueueFamilyIndex。
(12)创建CommandBuffer
创建CommandBuffer,使用vkBeginCommandBuffer和vkEndCommandBuffer记录CommandBuffer的操作,使用vkCmdBeginRenderPass和vkCmdEndRenderPass以及vkCmdDraw进行绘制
(13)创建信号量Semaphore
由于绘图和交换的操作的都是异步的,这里需要创建信号量来指示可用的image和渲染完成的状态。
这里使用ImageAvailableSemaphore和RenderFinishedSemaphore两个信号量来标识两者。
(14)在主循环中提交CommandBuffer并进行显示
每帧进行绘制时,首先通过vkAcquireNextImageKHR取得可用Image的索引,随后创建VkCommitInfo并使用vkQueueSubmit将绘制的CommandBuffer的命令提交到队列中,绘制完成后,将触发RenderFinishedSemaphore信号,这时使用使用VkPresentInfoKHR以及vkQueuePresentKHR方法将其显示到屏幕上。
3.mainLoop循环
处理窗口事件以及绘制。

一些特点总结:
(1)每个Vulkan对象VkXXX创建时需要配置一个对象对应的VkXXXCreateInfo,复杂的VkXXXCreateInfo可能包含子CreateInfo,每个CreateInfo的sType需要显式的进行指定。
(2)有些Vulkan对象在VkInstance销毁时自动释放,更多的对象需要手动调用对应的销毁函数来释放,可以将其使用一个VDeleter类包装,构造时传入对应的销毁函数,在VDeleter的析构函数中进行调用。
template <typename T>class VDeleter{public:VDeleter() : VDeleter([](T, VkAllocationCallbacks *) {}) {}VDeleter(std::function<void(T, VkAllocationCallbacks*)> deletef){this->deleter = [=](T obj) {deletef(obj, nullptr); };}VDeleter(const VDeleter<VkInstance> &instance, std::function<void(VkInstance, T, VkAllocationCallbacks *)> deletef){this->deleter = [&instance, deletef](T obj) {deletef(instance, obj, nullptr); };}VDeleter(const VDeleter<VkDevice> &instance, std::function<void(VkDevice, T, VkAllocationCallbacks *)> deletef){this->deleter = [&instance, deletef](T obj) {deletef(instance, obj, nullptr); };}~VDeleter(){cleanUp();}T* replace(){cleanUp();return &object;}operator T() const{return object;}const T* operator &() const{return &object;}void operator=(T rhs){if (rhs != object){cleanUp();object = rhs;}}template<typename V>bool operator==(V rhs){return object == T(rhs);}private:T object{ VK_NULL_HANDLE };std::function<void(T)> deleter;void cleanUp(){if (object != VK_NULL_HANDLE)deleter(object);object = VK_NULL_HANDLE;}};

运行结果:


可以看到1000行左右才画出来一个三角形,而且并没有使用VertexBuffer和IndexBuffer(顶点信息在Shader里进行了硬编码)。


1 0
原创粉丝点击