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

相关文章

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

MySQL Workbench 安装教程(保姆级)

《MySQLWorkbench安装教程(保姆级)》MySQLWorkbench是一款强大的数据库设计和管理工具,本文主要介绍了MySQLWorkbench安装教程,文中通过图文介绍的非常详细,对大... 目录前言:详细步骤:一、检查安装的数据库版本二、在官网下载对应的mysql Workbench版本,要是

通过Docker Compose部署MySQL的详细教程

《通过DockerCompose部署MySQL的详细教程》DockerCompose作为Docker官方的容器编排工具,为MySQL数据库部署带来了显著优势,下面小编就来为大家详细介绍一... 目录一、docker Compose 部署 mysql 的优势二、环境准备与基础配置2.1 项目目录结构2.2 基

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Spring 中使用反射创建 Bean 实例的几种方式

《Spring中使用反射创建Bean实例的几种方式》文章介绍了在Spring框架中如何使用反射来创建Bean实例,包括使用Class.newInstance()、Constructor.newI... 目录1. 使用 Class.newInstance() (仅限无参构造函数):2. 使用 Construc