Link

Vulkan 主要对象及其用途

  • VkInstance : Vulkan 上下文,用于访问驱动程序。
  • VkPhysicalDevice : 一个 GPU。用于查询物理 GPU 详细信息,例如特性、功能、内存大小等。
  • VkDevice : 您实际在上面执行操作的“逻辑” GPU 上下文。
  • VkBuffer : 一块 GPU 可见内存。
  • VkImage : 您可以写入和读取的纹理。
  • VkPipeline : 保存绘制所需的 GPU 状态。例如:着色器、光栅化选项、深度设置。
  • VkRenderPass : 保存您正在渲染到的图像的信息。所有绘制命令都必须在渲染通道内完成。仅在旧版 vkguide 中使用
  • VkFrameBuffer : 保存渲染通道的目标图像。仅在旧版 vkguide 中使用
  • VkCommandBuffer : 编码 GPU 命令。在 GPU 本身(而非驱动程序中)执行的所有操作都必须在 VkCommandBuffer 中编码。
  • VkQueue : 命令的执行“端口”。 GPU 将拥有一组具有不同属性的队列。有些只允许图形命令,有些只允许内存命令等。命令缓冲区通过将它们提交到队列来执行,这将把渲染命令复制到 GPU 上执行。
  • VkDescriptorSet : 保存将着色器输入连接到数据(例如 VkBuffer 资源和 VkImage 纹理)的绑定信息。可以将其视为一组您绑定一次的 GPU 端指针。
  • VkSwapchainKHR : 保存屏幕图像。它允许您将内容渲染到可见窗口中。 KHR 后缀表示它来自扩展,在本例中是 VK_KHR_swapchain
  • VkSemaphore : 同步 GPU 到 GPU 的命令执行。用于同步多个命令缓冲区的连续提交。
  • VkFence : 同步 GPU 到 CPU 的命令执行。用于了解命令缓冲区是否已在 GPU 上完成执行。

高级 Vulkan 应用程序流程

引擎初始化

首先,一切都被初始化。要初始化 Vulkan,您首先要创建一个 VkInstance。从 VkInstance,您查询机器中可用的 VkPhysicalDevice 句柄列表。例如,如果计算机同时具有独立 GPU 和集成显卡,则每个设备都会有一个 VkPhysicalDevice。在查询可用 VkPhysicalDevice 句柄的限制和特性后,您从中创建一个 VkDevice。使用 VkDevice,您可以从中获取 VkQueue 句柄,从而允许您执行命令。然后,您初始化 VkSwapchainKHR。与 VkQueue 句柄一起,您创建 VkCommandPool 对象,使您能够从中分配命令缓冲区。

资源初始化

一旦核心结构被初始化,您就可以初始化渲染所需的资源。材质被加载,并且您为渲染材质所需的着色器组合和参数创建一组 VkPipeline 对象。对于网格,您将其顶点数据上传到 VkBuffer 资源中,并将其纹理上传到 VkImage 资源中,确保图像处于“可读”布局。您还为您拥有的主要渲染通道创建 VkRenderPass 对象。例如,可能有一个用于主渲染的 VkRenderPass,另一个用于阴影通道。在真正的引擎上,其中大部分可以并行化并在后台线程中完成,尤其是在管线创建可能非常昂贵的情况下。

渲染循环

现在一切都已准备就绪,您可以首先向 VkSwapchainKHR 请求要渲染的图像。然后,您从 VkCommandBufferPool 分配一个 VkCommandBuffer,或重用已完成执行的已分配命令缓冲区,并“启动”命令缓冲区,这允许您将命令写入其中。接下来,您通过启动 VkRenderPass 开始渲染,这可以使用普通的 VkRenderPass 或使用动态渲染来完成。渲染通道指定您正在渲染到从交换链请求的图像。然后创建一个循环,在其中绑定 VkPipeline,绑定一些 VkDescriptorSet 资源(用于着色器参数),绑定顶点缓冲区,然后执行绘制调用。完成一个通道的绘制后,您结束 VkRenderPass。如果没有更多要渲染的内容,您结束 VkCommandBuffer。最后,您将命令缓冲区提交到队列进行渲染。这将开始在 GPU 上执行命令缓冲区中的命令。如果您想显示渲染结果,您可以将渲染到的图像“呈现”到屏幕上。由于执行可能尚未完成,因此您可以使用信号量来使图像呈现到屏幕上,直到渲染完成。

Vulkan 中渲染循环的伪代码

// Ask the swapchain for the index of the swapchain image we can render onto
int image_index = request_image(mySwapchain);

// Create a new command buffer
VkCommandBuffer cmd = allocate_command_buffer();

// Initialize the command buffer
vkBeginCommandBuffer(cmd, ... );

// Start a new renderpass with the image index from swapchain as target to render onto
// Each framebuffer refers to a image in the swapchain
vkCmdBeginRenderPass(cmd, main_render_pass, framebuffers[image_index] );

// Rendering all objects
for(object in PassObjects){

    // Bind the shaders and configuration used to render the object
    vkCmdBindPipeline(cmd, object.pipeline);
    
    // Bind the vertex and index buffers for rendering the object
    vkCmdBindVertexBuffers(cmd, object.VertexBuffer,...);
    vkCmdBindIndexBuffer(cmd, object.IndexBuffer,...);

    // Bind the descriptor sets for the object (shader inputs)
    vkCmdBindDescriptorSets(cmd, object.textureDescriptorSet);
    vkCmdBindDescriptorSets(cmd, object.parametersDescriptorSet);

    // Execute drawing
    vkCmdDraw(cmd,...);
}

// Finalize the render pass and command buffer
vkCmdEndRenderPass(cmd);
vkEndCommandBuffer(cmd);


// Submit the command buffer to begin execution on GPU
vkQueueSubmit(graphicsQueue, cmd, ...);

// Display the image we just rendered on the screen
// renderSemaphore makes sure the image isn't presented until `cmd` is finished executing
vkQueuePresent(graphicsQueue, renderSemaphore);

下一步:项目文件和库