Vulkan教程 - 10 创建图形管线

2024-08-21 19:58

本文主要是介绍Vulkan教程 - 10 创建图形管线,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        在我们完成管线创建之前,我们需要告诉Vulkan渲染将要用到的帧缓冲附件的信息。我们需要明确有多少颜色和深度缓冲,每个又有多少采样以及它们的内容应该如何通过渲染操作来进行处理。所有这些信息都包装在渲染通道(render pass)对象中,我们就创建一个新的方法createRenderPass,在initVulkan中调用它,且它在createGraphicsPipeline之前。

        我们这里仅有一个颜色缓冲附件,就是来自交换链的一个图像。

void createRenderPass() {VkAttachmentDescription colorAttachment = {};colorAttachment.format = swapChainImageFormat;colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
}

        颜色附件的format参数应该和交换链图像的格式匹配,且我们目前没有多重采样,所以就保持一个采样。

colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;

        loadOp和storeOp决定了在渲染前后和附件中的数据如何交互。loadOp有如下可选项:

        VK_ATTACHMENT_LOAD_OP_LOAD:保存附件当前存在的上下文;

        VK_ATTACHMENT_LOAD_OP_CLEAR:开始的时候清除值使其变成一个常数;

        VK_ATTACHMENT_LOAD_OP_DONT_CARE:当前存在的上下文是未定义的,且我们也不关心它们。

        我们这里将会在绘制新的帧之前使用清除操作来清除帧缓冲到黑色。storeOp只有两个可选项:

        VK_ATTACHMENT_STORE_OP_STORE:渲染内容将会被存储到内存且能后续读出;

        VK_ATTACHMENT_STORE_OP_DONT_CARE:渲染操作之后帧缓冲的内容会是未定义的;

        我们想要看到屏幕上渲染的三角形,所以我们选用存储操作:

colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

        loadOp和storeOp应用到颜色和深度数据,stencilLoadOp和stencilStoreOp应用到模板数据。我们的应用不会对模板缓冲做什么处理,所以加载和存储结果是无关的。

colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

        Vulkan中的材质和帧缓冲通过有特定像素格式的VkImage对象表示,但是内存中像素的布局可以改变,改变的依据是你要和图像做什么操作。

        几个最常用的布局如下:

        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:图像用作颜色附件;

        VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:图像呈现到交换链;

        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:图像用作内存复制操作的目的地 。

        我们当前要知道的是,图像需要转移到特定的布局,该布局适合我们将要进行的操作。

        initialLayout明确了在渲染通道开始之前图像将会拥有的布局。finalLayout明确了当渲染通道完成后要自动转移到的布局。用VK_IMAGE_LAYOUT_UNDEFINED作为initialLayout表示我们不关心之前图像布局。需要注意到该特殊值的一点是,图像内容不保证被保留,但是这没什么影响,因为我们反正还是要清除它的。我们想要图像为使用交换链渲染后的呈现准备就绪,所以用VK_IMAGE_LAYOUT_PRESENT_SRC_KHR作为finalLayout。

        单渲染通道可以由多个子通道组成。子通道是依赖于之前通道的帧缓冲内容的后续渲染操作,例如一个接一个应用的一系列后期处理效果。如果你把这些渲染操作合并成一个渲染通道,那么Vulkan能够重新对这些操作排序,并保存内存带宽以便更好地提升性能。我们的第一个三角形就还是用单个子通道。

        每个子通道引用一个或多个附件,这些引用就是些类似下面的VkAttachmentReference结构体:

VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

        attachment参数表明通过附件描述数组的索引来确定引用哪一个附件。我们的数组是由单个VkAttachmentDescription组成,因此索引就是0。布局参数表明了子通道使用该引用的时候,我们想让附件用的布局。当子通道开启的时候,Vulkan将会自动将附件转移到该布局。我们打算使用附件来当作一个颜色缓冲,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL布局会给我们最好的性能,就和它的名字的意思一样。

        子通道使用VkSubpassDescription结构体来描述:

VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;

        Vulkan将来可能支持计算子通道,所以我们必须显式说明这个是图形子通道。下面我们指明到颜色附件的引用:

subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;

        附件的索引就在片段着色器中用 layout(location = 0) out vec4 outColor直接引用。接着其他类型的可以被子通道引用的附件如下:

        pInputAttachments:从着色器读取的附件;

        pResolveAttachments:用于多重采样颜色附件的附件;

        pDepthStencilAttachment:用于深度和模板数据的附件;

        pPreserveAttachments:不是给这个子通道用的附件,但是数据又必须保存。

        现在附件和引用它的基础子通道已经都说过了,我们需要创建渲染通道了。创建一个新的类成员来存储VkRenderPass对象,就放在pipelineLayout变量上边:

VkRenderPass renderPass;

        渲染通道对象就可以根据VkRenderPassCreateInfo结构体信息创建,VkAttachmentReference对象通过数组索引引用附件:

VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {throw std::runtime_error("failed to create render pass!");
}

        如管线布局一样,渲染通道也是整个程序生命周期中都被引用的,所以在最后的cleanup中清理,紧跟管线布局清理之后调用:

vkDestroyRenderPass(device, renderPass, nullptr);

        现在我们能把前面章节所有的结构体和对象都组合起来创建图形管线了!现在回顾下我们都有哪些对象:

        着色器阶段:着色器模块定义了图形管线可编程阶段的功能;

        固定管线状态:所有的结构体定义了管线的固定功能阶段,例如输入组装,光栅器,视口和颜色混合;

        管线布局:由着色器引用的可以在绘制时更新的统一和可压入的值;

        渲染通道:由管线阶段引用的附件以及它们的用法。

        所有这些组合完整定义了图形管线的功能,所以我们现在开始填充VkGraphicsPipelineCreateInfo结构体,就在createGraphicsPipeline的末尾处。

VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;

        上面通过引用VkPipelineShaderStageCreateInfo进行起步,然后我们引用所有的结构体描述固定管线阶段:

pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = nullptr;  // optional
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = nullptr;  // optional

        之后是管线布局,它是一个Vulkan句柄而不是一个结构体指针:

pipelineInfo.layout = pipelineLayout;

        设置好到渲染通道的引用以及子通道的索引:

pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;

        本管线也可以使用别的渲染通道而不一定是这个特定的实例,但是要和renderPass兼容。兼容的要求是要在这里描述的,只是本教程不用而已。

pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;  // optional
pipelineInfo.basePipelineIndex = -1;  // optional

        实际上还有两个参数,basePipelineHandle和basePipelineIndex。Vulkan允许你通过派生一个已有的管线来创建新的图形管线。管线派生的想法是因为如果二者有很多相同的功能,这样比建立管线更加节省开销,而且从同一个父对象切换管线也会更快。你可以通过basePipelineHandle指明一个已存在管线的句柄,或者引用另一个管线,也就是要通过basePipelineIndex加上索引来创建的管线。现在只有一个管线,我们就指定一个空句柄和无效的索引。这些值仅仅在VkGraphicsPipelineCreateInfo的flags字段的VK_PIPELINE_CREATE_DERIVATIVE_BIT标记也指定的情况下才有用。

        最后一步,通过创建一个类成员来保存VkPipeline对象:

VkPipeline graphicsPipeline;

        最终可以创建图形管线了:

if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {throw std::runtime_error("failed to create graphics pipeline!");
}

        vkCreateGraphicsPipelines实际有更多的参数,它设计的时候就是接收多个VkGraphicsPipelineCreateInfo对象然后一次调用就会创建多个VkPipeline对象。

        第二个参数,我们已经传了VK_NULL_HANDLE,引用了一个可选的VkPipelineCache对象。管线缓冲可被用于存储和重用管线创建有关的数据,横跨多次vkCreateGraphicsPipelines调用,如果存储到了文件甚至横跨程序执行。这让极大提高管线传将速度成为可能,以后管线缓冲章节再深究。

        图形管线是所有常用绘制操作都要的,所以它也应该在程序结束的时候清理掉:

vkDestroyPipeline(device, graphicsPipeline, nullptr);

        这个就写在cleanup方法的第一行。现在运行程序,确认所有这些艰苦的工作能够最终成功创建管线。我们现在离看到屏幕显示东西已经很近了(其实快一百三十页了,居然还没看到三角形),下面的章节会设置来自交换链的真正的的帧缓冲并准备绘制命令。

这篇关于Vulkan教程 - 10 创建图形管线的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094133

相关文章

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

idea+spring boot创建项目的搭建全过程

《idea+springboot创建项目的搭建全过程》SpringBoot是Spring社区发布的一个开源项目,旨在帮助开发者快速并且更简单的构建项目,:本文主要介绍idea+springb... 目录一.idea四种搭建方式1.Javaidea命名规范2JavaWebTomcat的安装一.明确tomcat

全网最全Tomcat完全卸载重装教程小结

《全网最全Tomcat完全卸载重装教程小结》windows系统卸载Tomcat重新通过ZIP方式安装Tomcat,优点是灵活可控,适合开发者自定义配置,手动配置环境变量后,可通过命令行快速启动和管理... 目录一、完全卸载Tomcat1. 停止Tomcat服务2. 通过控制面板卸载3. 手动删除残留文件4.

Python的pandas库基础知识超详细教程

《Python的pandas库基础知识超详细教程》Pandas是Python数据处理核心库,提供Series和DataFrame结构,支持CSV/Excel/SQL等数据源导入及清洗、合并、统计等功能... 目录一、配置环境二、序列和数据表2.1 初始化2.2  获取数值2.3 获取索引2.4 索引取内容2

python依赖管理工具UV的安装和使用教程

《python依赖管理工具UV的安装和使用教程》UV是一个用Rust编写的Python包安装和依赖管理工具,比传统工具(如pip)有着更快、更高效的体验,:本文主要介绍python依赖管理工具UV... 目录前言一、命令安装uv二、手动编译安装2.1在archlinux安装uv的依赖工具2.2从github

C#实现SHP文件读取与地图显示的完整教程

《C#实现SHP文件读取与地图显示的完整教程》在地理信息系统(GIS)开发中,SHP文件是一种常见的矢量数据格式,本文将详细介绍如何使用C#读取SHP文件并实现地图显示功能,包括坐标转换、图形渲染、平... 目录概述功能特点核心代码解析1. 文件读取与初始化2. 坐标转换3. 图形绘制4. 地图交互功能缩放

Git打标签从本地创建到远端推送的详细流程

《Git打标签从本地创建到远端推送的详细流程》在软件开发中,Git标签(Tag)是为发布版本、标记里程碑量身定制的“快照锚点”,它能永久记录项目历史中的关键节点,然而,仅创建本地标签往往不够,如何将其... 目录一、标签的两种“形态”二、本地创建与查看1. 打附注标http://www.chinasem.cn

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配