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)被发出信号,或者达到指定的超时时间。信号量用于同步不同队列之间的操作。