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_swapchainVkSemaphore
: 同步 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);
下一步:项目文件和库