Vulkan学习(二)

 1.CreateInfo

        vulkan中有众多的createInfo信息,信息中的某些字段需要了解一下

       1.1VkImageCreateInfo 图像创建信息

VkImageCreateInfo imageCI{};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D;	// 2D图形
imageCI.format = depthFormat;			// 深度的格式,在交换链中已经获取了
imageCI.extent = { width, height, 1 };  // 图像的宽度和高度
imageCI.mipLevels = 1;					// 只有一个mipmap级别
imageCI.arrayLayers = 1;				// 只有一个层
imageCI.samples = VK_SAMPLE_COUNT_1_BIT;	// 单重采样
imageCI.tiling = VK_IMAGE_TILING_OPTIMAL;	// 图像分tiling进行优化
imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; // 用作深度模板附件

        tiling:在移动端似乎有这个优化过程,原理大概是要渲染一张大图,但是手机的资源是有限的,所以就需要将一张大图分成很多的小图(tile)去渲染,这就是tiling的作用(个人猜想,需要查找资料验证)

2.多Pass过程

        多pass中常用案例是postprocess,例如抗锯齿、描边、景深等,在vulkan中多pass需要一些特别的流程,到目前为止我所见到的使用方式有两种:

        1.设定一个renderpass的过程,这个的前提是将所有的subpass都放入到一个renderpass描述中。

// 开始第一个pass
cmd.beginRenderPass(&renderPassBeginInfo, vk::SubpassContents::eInline);
cmd.drawIndexed(...);
// 切换到下一个pass
cmd.nextSubpass(vk::SubpassContents::eInline);
cmd.drawIndexed(...);
// 结束所有的pass
cmd.endRenderPass();

          2.创建多个renderpass,在使用的时候切换,摘自GitHub - SaschaWillems/Vulkan: C++ examples for the Vulkan graphics API 的offscreen案例

	void buildCommandBuffers()
	{
		VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();

		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
		{
			VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));

			/*
				First render pass: Offscreen rendering
			*/
			{
				VkClearValue clearValues[2];
				clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } };
				clearValues[1].depthStencil = { 1.0f, 0 };

				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
				renderPassBeginInfo.renderPass = offscreenPass.renderPass;
				renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer;
				renderPassBeginInfo.renderArea.extent.width = offscreenPass.width;
				renderPassBeginInfo.renderArea.extent.height = offscreenPass.height;
				renderPassBeginInfo.clearValueCount = 2;
				renderPassBeginInfo.pClearValues = clearValues;

				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

				VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f);
				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);

				VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0);
				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);

				// Mirrored scene
				vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.shaded, 0, 1, &descriptorSets.offscreen, 0, NULL);
				vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shadedOffscreen);
				models.example.draw(drawCmdBuffers[i]);

				vkCmdEndRenderPass(drawCmdBuffers[i]);
			}

			/*
				Note: Explicit synchronization is not required between the render pass, as this is done implicit via sub pass dependencies
			*/

			/*
				Second render pass: Scene rendering with applied radial blur
			*/
			{
				VkClearValue clearValues[2];
				clearValues[0].color = defaultClearColor;
				clearValues[1].depthStencil = { 1.0f, 0 };

				VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
				renderPassBeginInfo.renderPass = renderPass;
				renderPassBeginInfo.framebuffer = frameBuffers[i];
				renderPassBeginInfo.renderArea.extent.width = width;
				renderPassBeginInfo.renderArea.extent.height = height;
				renderPassBeginInfo.clearValueCount = 2;
				renderPassBeginInfo.pClearValues = clearValues;

				vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

				VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
				vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);

				VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
				vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);

				if (debugDisplay)
				{
					// Display the offscreen render target
					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.textured, 0, 1, &descriptorSets.mirror, 0, nullptr);
					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debug);
					vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0);
				} else {
					// Render the scene
					// Reflection plane
					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.textured, 0, 1, &descriptorSets.mirror, 0, nullptr);
					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.mirror);
					models.plane.draw(drawCmdBuffers[i]);
					// Model
					vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.shaded, 0, 1, &descriptorSets.model, 0, nullptr);
					vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.shaded);
					models.example.draw(drawCmdBuffers[i]);
				}

				drawUI(drawCmdBuffers[i]);

				vkCmdEndRenderPass(drawCmdBuffers[i]);
			}

			VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
		}
	}

上面的案例的过程

vkBeginCommandBuffer(...)

vkCmdBeginRenderPass(第一个renderpass)
draw(...)
vkCmdEndRenderPass(...)

vkCmdBeginRenderPass(第二个renderpass)
draw(...)
vkCmdEndRenderPass(...)

vkEndCommandBuffer(...)

        上述过程中第一种方式效率优于第二种,避免了很多的资源切换

3、拷贝图像

将图像复制一份,复制的过程中可能会复制图像的一部分,或者缩放图像,还有包括图像的过滤方式(linear、nearest)等,代码如下:

// 创建 VkImageBlit 结构体
VkImageBlit blit = {};
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;

// 原图像的哪个baseArrayLayer的图层,这个图层的哪个MipLevel层级的图作为拷贝的源头
blit.srcSubresource.mipLevel = 0;
blit.srcSubresource.baseArrayLayer = 0;
blit.srcSubresource.layerCount = 1;

// 原图像的拷贝的区域
blit.srcOffsets[0] = {0, 0, 0};
blit.srcOffsets[1] = {srcWidth, srcHeight, 1};

blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;

// 目标图像的哪个baseArrayLayer的图层,这个图层的哪个MipLevel层级的图作为拷贝的目标
blit.dstSubresource.mipLevel = 0;
blit.dstSubresource.baseArrayLayer = 0;
blit.dstSubresource.layerCount = 1;

// 目标图像写入的区域
blit.dstOffsets[0] = {0, 0, 0};
blit.dstOffsets[1] = {dstWidth, dstHeight, 1};

// 记录 vkCmdBlitImage 命令
vkCmdBlitImage(
    commandBuffer,
    srcImage,                                    // VkImage 原图像
    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,        // 原图像的布局
    dstImage,                                    // VkImage 目标图像
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,        // 目标图像的布局
    1,
    &blit,
    VK_FILTER_LINEAR);                            // 过滤方式(线性插值,还是最邻近采样)

4、管线屏障

        管线屏障的作用:

        1)同步操作

        在 Vulkan 里,图形渲染管线包含多个阶段,如顶点处理、光栅化、片段处理等。不同阶段可能会并发执行,这就可能导致数据竞争问题。vkCmdPipelineBarrier 可以在管线中插入同步点,确保在屏障之前的所有操作完成后,才会开始执行屏障之后的操作。

        2)内存屏障

        它能够控制内存访问的顺序和可见性。当一个操作写入数据到内存,而另一个操作需要读取这些数据时,vkCmdPipelineBarrier 可以确保写入操作完成并且数据对读取操作可见。

        3)图像布局转换

        在 Vulkan 中,图像可以有不同的布局(VkImageLayout),不同的操作要求图像处于特定的布局。例如,在进行图像传输操作时,图像需要处于 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL 或 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 布局;在进行渲染操作时,图像可能需要处于 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 布局。vkCmdPipelineBarrier 可以用于在不同的图像布局之间进行转换。

        4)队列所有权转移

        当一个资源(如图像或缓冲区)的所有权需要从一个队列族转移到另一个队列族时,vkCmdPipelineBarrier 可以确保这种转移的正确执行,保证数据的一致性。

        管线屏障作用的目标:

        VkMemoryBarrier结构体数组的指针,用于全局内存屏障。

        VkBufferMemoryBarrier结构体数组的指针,用于缓冲区内存屏障。

        VkImageMemoryBarrier结构体数组的指针,用于图像内存屏障。

void vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,
    VkPipelineStageFlags          srcStageMask, //在这个命令执行之前,需要完成的管线阶段
    VkPipelineStageFlags          dstStageMask, //在这个命令执行之后,剋以开始的管线阶段
    VkDependencyFlags             dependencyFlags,
    uint32_t                      memoryBarrierCount,
    const VkMemoryBarrier*        pMemoryBarriers,
    uint32_t                      bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*  pBufferMemoryBarriers,
    uint32_t                      imageMemoryBarrierCount,
    const VkImageMemoryBarrier*   pImageMemoryBarriers);



// 创建 VkImageMemoryBarrier 结构体
VkImageMemoryBarrier imageBarrier = {};
imageBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;

// 原阶段是数据的传输写入
imageBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
// 目标阶段是着色器的读取(只有写入数据完成,才能进行后续的读取操作)
imageBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

// 布局转换
imageBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imageBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

// 队列所有权转移
imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;

imageBarrier.image = image;
imageBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;

imageBarrier.subresourceRange.baseMipLevel = 0;
imageBarrier.subresourceRange.levelCount = 1;
imageBarrier.subresourceRange.baseArrayLayer = 0;
imageBarrier.subresourceRange.layerCount = 1;

// 设置图像布局转换的信息
vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_TRANSFER_BIT,  // 执行完成之前的传输操作,例如拷贝图像,转换图像布局等
    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,   // 执行完成之后才能在着色器中使用
    0,
    0, nullptr,
    0, nullptr,
    1, &imageBarrier
);

        上面的代码中只有等待原VK_PIPELINE_STAGE_TRANSFER_BIT阶段的VK_ACCESS_TRANSFER_WRITE_BIT写入完成,才能在VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT阶段执行VK_ACCESS_SHADER_READ_BIT读取操作

总结:vkCmdPipelineBarrier 是 Vulkan 中实现同步和内存管理的关键命令,它可以确保管线操作的顺序和数据的一致性,同时支持图像布局转换和队列族所有权转移等重要功能。在编写 Vulkan 应用程序时,合理使用 vkCmdPipelineBarrier 是保证程序正确性和性能的关键。

5、 Vulkan中的wait有哪些

    下面这些等待是为了cpu和gpu之间的同步,cpu等待gpu完成相关的操作后才能顺序执行后续的操作。

 1)vkDeviceWaitIdle

        阻塞调用线程,直到指定的逻辑设备上所有队列完成之前提交的所有命令,即设备进入空闲状态。常用于在资源释放或销毁前,确保设备不再使用这些资源,避免资源竞争。

 2)vkQueueWaitIdle

        阻塞调用线程,直到指定队列完成之前提交的所有命令,即该队列进入空闲状态。相比 vkDeviceWaitIdle,它只等待特定队列,粒度更细。

   3) vkWaitForFences

        阻塞调用线程,直到指定的一个或多个栅栏(Fence)被发出信号(signaled),或者达到指定的超时时间。栅栏用于同步 CPU 和 GPU,可确保在 CPU 执行某些操作前,GPU 完成特定任务。

   4) vkWaitSemaphores

        阻塞调用线程,直到指定的一个或多个信号量(Semaphore)被发出信号,或者达到指定的超时时间。信号量用于同步不同队列之间的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值