Vulkan 学习(15)---- Vulkan 完整渲染流程

完整渲染流程(在屏渲染)

初始化流程:

  • 创建 Vulkan Instance, 启用 ValidationLayer
  • 根据 Instance 创建 PhysicalDevice 同时查询并获取所需的 QueueFamilyIndex
  • 创建 Logical Device, 获取 Device Queue
  • 创建 SwapChain 获取 SwapChain 中的 vkImage,同时创建 ImageView
  • 创建 RenderPass
  • 创建 GraphicPipeLine
  • 创建 FrameBuffer
  • 创建 CommandPool, 分配CommandBuffer
  • 创建 Sync Object(Fence/Semaphore)

绘制流程:

  • 录制 commandBuffer
  • 提交 commandBuffer
  • 等待绘制完成
创建基本上下文

从创建 Vulkan Instance, 到获取 Device Queue 的过程比较基础,参考下面的博客

参考:
Vulkan Instance
Vulkan 物理设备和队列族
Vulkan 逻辑设备

创建 Surface 和 SwapChain
  1. 创建 vkSurfaceKHR 对象
    Vulkan 中,vkSurfaceKHR 是一个抽象的表面对象,用于将渲染结果呈现到屏幕上,SwapChain 依赖于 vkSurfaceKHR,因为它需要知道渲染目标表面以及如何与之交互

vkSurfaceKHR 对象的作用如下:

  • 连接 Vulkan 和窗口系统
    Windows 下使用 win32 对接,Android 下使用 ANativeWindow 对接

  • 管理呈现模式
    比如是否使用V-sync,窗口模式等

  • 查询表面能力
    在创建 SwapChain 之前,Vulkan 需要查询表面属性(比如支持的格式,大小和呈现模式等)

下面的参考代码是从 vkSurfaceKHR 对象中获取颜色和大小等信息

SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);

    uint32_t formatCount;
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

    if (formatCount != 0) {
        details.formats.resize(formatCount);
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
    }

    uint32_t presentModeCount;
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
    if (presentModeCount != 0) {
        details.presentModes.resize(presentModeCount);
        vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
    }

    return details;
}
  1. 创建 SwapChain 对象
    根据创建的 vkSurfaceKHR 对象查询出的表面属性(支持的格式,大小,色域等),创建出SwapChain
    参考:Vulkan SwapChain 创建

SwapChain 在创建成功之后,内部已经包含了 vkImage 对象,但是这些 VkImage 是由 SwapChain 管理的,不能直接访问,需要调用 Vulkan API 来获取
这些 vkImage 对象的声明周期和 SwapChain 绑定,当 SwapChain 销毁时,对应的 vkImage 对象也会销毁

使用下面的方法获取SwapChain中的vkImage

uint32_t imageCount;
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, nullptr); 

std::vector<VkImage> swapchainImages(imageCount);
vkGetSwapchainImagesKHR(device, swapchain, &imageCount, swapchainImages.data()); 

需要注意的是,如果窗口变化或者失效,此时需要重建 SwapChain, 对应的 vkImage 也可能失效,需要重新获取

WSI 说明

VulkanWSIWindow System Integration,窗口系统集成)主要集中在SurfaceSwapChain,呈现(Presentation) 和平台适配层等多个部分

  • vkSurfaceKHR
    表面属性(支持的格式,大小等)

  • VkSwapchainKHR
    提供一组 vkImage 用于渲染,控制呈现模式(MailBox or Immedaite)并处理窗口大小的变化

  • 呈现(vkQueuePresentKHR
    用于将 SwapChain 中的图像呈现到屏幕

创建 RenderPass 和 FrameBuffer

RenderPass 用于描述一次完整的渲染流程,说明了对 vkImage 的操作关系,vkImage 的操作关系是以颜色,模板、深度附件的形式关联到 RenderPassRenderPass 只描述渲染流程,不包括渲染数据,渲染数据需要以 FrameBuffer 形式来获取到

参考:Vulkan RenderPass 创建

这里创建从 SwapChain 获取到的 vkImagevkImageView,在 RenderPass 中定义为颜色附件(作为渲染目标)

vkFrameBuffer 类似于一个图像的容器,里面包含一系列的vkImageViewvkImageView 定义了对应的 vkImage 的解析方式
同时也关联到了对应的vkImage,结合 RenderPassvkImage 的操作方法,就定义了一次渲染流程

参考:Vulkan FrameBuffer 创建

创建 GraphicPipeLine

GraphicPipeLine 用于对应图形渲染管线,创建 GraphicPipeLine 也就是设置了图像管线的工作状态
最重要的,GraphicPipeLine 还包含对 VertexShaderFragementShader 的设置

参考:Vulkan Pipeline 创建

创建 CommandPool 和 CommandBuffer

Vulkan 中,指令缓存(Command buffer)是用于记录和存储一系列绘制和计算指令的对象
这些指令将在 GPU 上执行,可以执行不同类型的工作,包括绑定顶点缓存、绑定流水线、录制渲染通道指令、设置视口和裁切矩形,设置绘制指令,执行图像和缓存内容的复制操作等
Command bufferCommand buffer Pool 中分配得到,Command buffer Pool是根据创建逻辑设备的queueFamilyIndex 作为参数创建的

参考:Vulkan CommandBuffer 创建

录制 Command 命令

Command 命令包括绑定顶点缓存(可选)、绑定流水线、设置视口和裁切矩形、录制渲染通道指令、设置绘制指令等
recordCommandBuffer 的过程以 vkBeginCommandBuffer 开始,以 vkEndCommandBuffer 结束

// vkCmdBeginRenderPass
// vkCmdBindPipeline
// vkCmdSetViewport vkCmdSetScissor
// vkCmdDraw
// vkCmdEndRenderPass
// vkCmdEndRenderPass
voidrecordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
    VkCommandBufferBeginInfo beginInfo{};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

    if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
        throw std::runtime_error("failed to begin recording command buffer!");
    }

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

    VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
    renderPassInfo.clearValueCount = 1;
    renderPassInfo.pClearValues = &clearColor;

    vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

    VkViewport viewport{};
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = static_cast<float>(swapChainExtent.width);
    viewport.height = static_cast<float>(swapChainExtent.height);
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
    vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

    VkRect2D scissor{};
    scissor.offset = { 0, 0 };
    scissor.extent = swapChainExtent;
    vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

    vkCmdDraw(commandBuffer, 3, 1, 0, 0);
    vkCmdEndRenderPass(commandBuffer);

    if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
        throw std::runtime_error("failed to record command buffer!");
    }
}
同步提交渲染到屏幕

Vulkan渲染-呈现 流程涉及到两个关键的信号量:
imageAvailableSemaphore(由 vkAcquireNextImageKHR 触发),表示 交换链图像已经可用(即 GPU 已完成该图像的先前呈现,可以开始渲染)
renderFinishedSemaphore(由 vkQueueSubmit 触发)表示渲染完成,可以提交给 vkQueuePresentKHR 显示
onScreenRender

vkQueuePresentKHR 为什么需要 Semaphore ?

  1. VulkanPresent 是一个队列操作(vkQueuePresentKHR 提交到 VkQueue),意味着它由 GPU 驱动调度执行
    它需要等待 GPU 渲染完成(通过 pWaitSemaphores),确保图像可用后再显示,它可能涉及 GPU 端的图像布局转换(如从 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 切换到 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR

  2. 最终,vkQueuePresentKHR 会将图像提交给窗口系统的合成器,这部分操作通常由 GPU 驱动与窗口系统协作完成
    基于此,vkQueuePresentKHR 要实现 GPU 之间队列的同步,所以需要引入一个 Semaphore

除此之前在 vkAcquireNextImageKHR 之前,还需要等待 vkQueueSubmit 产生的 Fence:

为什么在 Semaphore 的前提下还需要 Fence
Vulkan 的交换链(Swapchain)通常有 2 ~ 3 张图像(VK_SWAPCHAIN_IMAGE_COUNT),用于多缓冲
vkQueuePresentKHR 提交图像后,GPU 不会立即释放它,而是等待垂直同步(VSync)或窗口系统完成显示
如果过早复用同一张图像(即 vkAcquireNextImageKHR 返回的 imageIndex 仍被 GPU 使用),会导致写入冲突或者数据竞争

vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &inFlightFence);

uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain,UINT64_MAX, \
    imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);

vkResetCommandBuffer(commandBuffer, 0);
recordCommandBuffer(commandBuffer, imageIndex);

VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

// wait a semaphore and trigger an fence
VkSemaphore waitSemaphores[] = { imageAvailableSemaphore };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;

submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;

VkSemaphore signalSemaphores[] = { renderFinishedSemaphore };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;

if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
    throw std::runtime_error("failed to submit draw command buffer!");
}

VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;

presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;

VkSwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;

vkQueuePresentKHR(presentQueue, &presentInfo);
Fence 和 Semaphore 区别

Fence 适用于 CPU-GPU 同步,而 Semaphore 适用于 GPU-GPU 同步

  • Fence 主要用于 CPU 等待 GPU 完成某个操作(例如,确保渲染完成后再更新资源)。
  • Semaphore 主要用于 GPU 内部不同队列或操作之间的同步(例如,渲染完成后再呈现)。

vkQueuePresentKHR 本身是一个 GPU 操作,它需要等待 另一个 GPU 操作(如 vkQueueSubmit 的渲染)完成,因此 semaphore 是更合适的同步机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值