vulkan笔记_2

vulkan笔记_2

renderpass

在我们完成管线的创建工作之前,我们需要告诉Vulkan渲染时候使用的framebuffer帧缓冲区附件相关信息。我们需要指定多少个颜色和深度缓冲区将会被使用,指定多少个采样器被用到及在整个渲染操作中相关的内容如何处理。所有的这些信息都被封装在一个叫做 render pass 的对象中

先对需要处理的附件进行一个整体的描述,意思大致为,我准备怎么处理这个附件

在Vulkan中,用具有特定像素格式的VkImage 表示纹理(texture)和FrameBuffer,而像素在内存中的布局(layout)随着我们使用Image 的目的不同是可以改变的。一些常见的layout有:
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: Images 用作 color attachment
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 表示一个要被显示的swap chain image源
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: images 作为内存拷贝操作的目的
我们将在文理(texture)部分再详细讲解这部分,现在仅需要明确的是image 在每个后续的操作中都要转变成特定的layout。
initial / finalLaout表示渲染前后image的layout, VK_IMAGE_LAYOUT_UNDEFINED表示我们不在乎之前的layout,而VK_IMAGE_LAYOUT_PRESENT_SRC_KHR表示在渲染后,使用Swpa Chain 时,image 处于可显示的layout。

// 定义renderpass对这个颜色附件的行为
		VkAttachmentDescription colorAttachment = {};
		colorAttachment.format = swapChainImageFormat;     //格式与swapchain一致
		colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;   // 不用多重采样

        //loadOp和storeOp决定了渲染前和渲染后数据在对应附件的操作行为。对于 loadOp 我们有如下选项:

        //    VK_ATTACHMENT_LOAD_OP_LOAD : 保存已经存在于当前附件的内容
        //    VK_ATTACHMENT_LOAD_OP_CLEAR : 起始阶段以一个常量清理附件内容
        //    VK_ATTACHMENT_LOAD_OP_DONT_CARE : 存在的内容未定义,忽略它们
        //在绘制新的一帧内容之前,我们要做的是使用清理操作来清理帧缓冲区framebuffer为黑色。同时对于 storeOp 仅有两个选项:

        //    VK_ATTACHMENT_STORE_OP_STORE : 渲染的内容会存储在内存,并在之后进行读取操作
        //    VK_ATTACHMENT_STORE_OP_DONT_CARE : 帧缓冲区的内容在渲染操作完毕后设置为undefined
		colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;   
		colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
        //loadOp和storeOp应用在颜色和深度数据,同时stencilLoadOp / stencilStoreOp应用在模版数据。我们的应用程序不会做任何模版缓冲区的操作,所以它的loading和storing无关紧要。
		colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
		colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
        //initialLayout指定图像在开始进入渲染通道render pass前将要使用的布局结构。finalLayout指定当渲染通道结束自动变换时使用的布局
		colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
		colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

但是在实际上,renderpass并不直接处理附件,他会转手交给subpass处理

        // 引用的附着 给后面subpass用的
		VkAttachmentReference colorAttachmentRef = {};
		colorAttachmentRef.attachment = 0;
        // 在渲染中的布局
		colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
		VkSubpassDescription subpass = {};
        // 指定这是一个图像sunpass,不是计算的
		subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
		subpass.colorAttachmentCount = 1;
		subpass.pColorAttachments = &colorAttachmentRef;

实际上最后subpass是通过pipeline完成渲染的

在这里插入图片描述
因为只有一个subpass,随意是0,由此可见是不是几个subpass就需要几个pipeline啊
在这里插入图片描述

subpass之间有没有依赖关系

        // 依赖关系,有些复杂的程序,当前的附件渲染可能需要前一个附件渲染的结果只
		VkSubpassDependency dependency = {};
		dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
		dependency.dstSubpass = 0;
		dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
		dependency.srcAccessMask = 0;
		dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
		dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

创建renderpass

		VkRenderPassCreateInfo renderPassInfo = {};
		renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
		renderPassInfo.attachmentCount = 1;
		renderPassInfo.pAttachments = &colorAttachment;
		renderPassInfo.subpassCount = 1;
		renderPassInfo.pSubpasses = &subpass;
		renderPassInfo.dependencyCount = 1;
		renderPassInfo.pDependencies = &dependency;

		if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
			throw std::runtime_error("failed to create render pass!");
		}

但是注意,虽然我们创建了renderpass,但是实际上现在都是空话,因为我们只是设想了怎么处理附件,但是还没有拿到附件,与附件产生关系

			VkRenderPassBeginInfo renderPassInfo = {};
			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
			renderPassInfo.renderPass = renderPass;
			renderPassInfo.framebuffer = swapChainFramebuffers[i];
			renderPassInfo.renderArea.offset = {0, 0};
			renderPassInfo.renderArea.extent = swapChainExtent;

应该是在这里,最后开始renderpass时,产生的关系

创建图形管线

省略

FrameBuffer

	void createFramebuffers() {
		swapChainFramebuffers.resize(swapChainImageViews.size());

		for (size_t i = 0; i < swapChainImageViews.size(); i++) {
			VkImageView attachments[] = {
					swapChainImageViews[i]
			};

			VkFramebufferCreateInfo framebufferInfo = {};
			framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
			framebufferInfo.renderPass = renderPass;
			framebufferInfo.attachmentCount = 1;
			framebufferInfo.pAttachments = attachments;
			framebufferInfo.width = swapChainExtent.width;
			framebufferInfo.height = swapChainExtent.height;
			framebufferInfo.layers = 1;

			if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
				throw std::runtime_error("failed to create framebuffer!");
			}
		}
	}

如你所见,创建framebuffers是非常直接的。首先需要指定framebuffer需要兼容的renderPass。我们只能使用与其兼容的渲染通道的帧缓冲区,这大体上意味着它们使用相同的附件数量和类型。
attachmentCount和pAttachments参数指定在渲染通道的pAttachment数组中绑定到相应的附件描述的VkImageView对象。
width和height参数是容易理解的,layer是指定图像数组中的层数。我们的交换链图像是单个图像,因此层数为1。

描述符集

我们现在可以将任意属性传递给每个顶点的顶点着色器使用。但是全局变量呢

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

比如shader中有一个模型视图投影矩阵uniform UniformBufferObject,怎么给他传值呢

代码中对应创建一个这样的结构

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

老规矩,这种数据应该是保存在内存里的,跟之前的顶级数据一样,然后去内存中访问。
但是uniform数据不能直接访问,需要用一个中间工具DescritorSet,创建unformbuffer,以及更新,先省略

void createDescriptorSetLayout() {
		// 首先告诉set,你跟shader的那个数据绑定
		VkDescriptorSetLayoutBinding uboLayoutBinding = {};
        // 在shader中定义了binding
		uboLayoutBinding.binding = 0;
		uboLayoutBinding.descriptorCount = 1;
		uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
		uboLayoutBinding.pImmutableSamplers = nullptr;
		uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

		VkDescriptorSetLayoutBinding samplerLayoutBinding = {};
        // 在shader中指定
		samplerLayoutBinding.binding = 1;
		samplerLayoutBinding.descriptorCount = 1;
		samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		samplerLayoutBinding.pImmutableSamplers = nullptr;
		samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

		std::array<VkDescriptorSetLayoutBinding, 2> bindings = {uboLayoutBinding, samplerLayoutBinding};
		// 根据binding创建layoutinfo,布局的意思是不单单指定一个set,因为后面会用到很多set,一个个去指定绑定点不现实,所以整一个布局,给set应用这个布局,他就知道了
		VkDescriptorSetLayoutCreateInfo layoutInfo = {};
		layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
		layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
		layoutInfo.pBindings = bindings.data();

		if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
			throw std::runtime_error("failed to create descriptor set layout!");
		}
	}

同时,实际上最后的操作都是发生在pipeline中,所以我们需要在创建管线的时候指定描述符集合的布局,用以告知Vulkan着色器将要使用的描述符。描述符布局在管线布局对象中指定。修改VkPipelineLayoutCreateInfo引用布局对象

		VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
		pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
		pipelineLayoutInfo.setLayoutCount = 1;
        // 因为所有的操作都在管线中执行,需要告诉vulkan 要用到哪些描述符集
		pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;

		if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
			throw std::runtime_error("failed to create pipeline layout!");
		}

创建graphicspipeline用到

现在set知道了为谁打工,那set这个打工仔怎么创建呢,需要一个pool,同意分配

	void createDescriptorPool() {
		std::array<VkDescriptorPoolSize, 2> poolSizes = {};
		poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
		poolSizes[0].descriptorCount = 1;
		poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		poolSizes[1].descriptorCount = 1;

		VkDescriptorPoolCreateInfo poolInfo = {};
		poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
		poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
		poolInfo.pPoolSizes = poolSizes.data();
		poolInfo.maxSets = 1;

		if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
			throw std::runtime_error("failed to create descriptor pool!");
		}
	}

前面创建了两种setlayout,证明需要两种set,所以这里有两个pool

创建打工仔,告诉去哪里打工

void createDescriptorSet() {
		VkDescriptorSetLayout layouts[] = {descriptorSetLayout};
		VkDescriptorSetAllocateInfo allocInfo = {};
		allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
		allocInfo.descriptorPool = descriptorPool;
		allocInfo.descriptorSetCount = 1;
		allocInfo.pSetLayouts = layouts;

		if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) {
			throw std::runtime_error("failed to allocate descriptor set!");
		}
        // 打工场所的地址信息
		VkDescriptorBufferInfo bufferInfo = {};
		bufferInfo.buffer = uniformBuffer;
		bufferInfo.offset = 0;
		bufferInfo.range = sizeof(UniformBufferObject);

		VkDescriptorImageInfo imageInfo = {};
		imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
		imageInfo.imageView = textureImageView;
		imageInfo.sampler = textureSampler;

		std::array<VkWriteDescriptorSet, 2> descriptorWrites = {};
        // 写描述符,告诉谁去哪拿
		descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		descriptorWrites[0].dstSet = descriptorSet;  //描述符集合,注意这里是集合哦,因为有两种set,随意,虽然叫descriptSet,但是里面是可能有多个的,具体看怎么分配的,需要几个set分配几个
		descriptorWrites[0].dstBinding = 0;     //描述符集合中描述符的索引
		descriptorWrites[0].dstArrayElement = 0;
		descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
		descriptorWrites[0].descriptorCount = 1;
		descriptorWrites[0].pBufferInfo = &bufferInfo;

		descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
		descriptorWrites[1].dstSet = descriptorSet;
		descriptorWrites[1].dstBinding = 1;
		descriptorWrites[1].dstArrayElement = 0;
		descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
		descriptorWrites[1].descriptorCount = 1;
		descriptorWrites[1].pImageInfo = &imageInfo;

		vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
	}

还没结束,知道了为谁打工,去哪里打工拿东西,最后得到的东西怎么给雇主呢
因为最后实际操作发生在pipeline中,所以经过pipeline沟通,pipeline根据前面的pipelineLayout知道东西用在哪里。

vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值