[译]Vulkan教程(18)命令buffers
[译]Vulkan教程(18)命令buffers
Command buffers 命令buffer
Commands in Vulkan, like drawing operations and memory transfers, are not executed directly using function calls. You have to record all of the operations you want to perform in command buffer objects. The advantage of this is that all of the hard work of setting up the drawing commands can be done in advance and in multiple threads. After that, you just have to tell Vulkan to execute the commands in the main loop.
Vulkan中的命令,例如绘制操作和内存转移,不是直接通过函数调用来执行的。你必须几所有你想要的操作到命令buffer对象里。这样做的优势是,所有的设置绘制命令的艰苦努力都可以提前完成,还能多线程完成。之后,你只需告诉Vulkan去执行主循环中的命令即可。
Command pools 命令池
We have to create a command pool before we can create command buffers. Command pools manage the memory that is used to store the buffers and command buffers are allocated from them. Add a new class member to store a VkCommandPool
:
我们必须创建命令池before我们能创建命令buffer。命令池管理内存that用于保存buffer,命令buffer从命令池中申请。添加新类成员to保存VkCommandPool
:
VkCommandPool commandPool;
Then create a new function createCommandPool
and call it from initVulkan
after the framebuffers were created.
然后创建一个新函数createCommandPool
,在initVulkan
中调用它after帧缓存创建后。
1 void initVulkan() { 2 createInstance(); 3 setupDebugCallback(); 4 createSurface(); 5 pickPhysicalDevice(); 6 createLogicalDevice(); 7 createSwapChain(); 8 createImageViews(); 9 createRenderPass(); 10 createGraphicsPipeline(); 11 createFramebuffers(); 12 createCommandPool(); 13 } 14 15 ... 16 17 void createCommandPool() { 18 19 }
Command pool creation only takes two parameters:
创建命令池只需要2个参数:
QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); poolInfo.flags = 0; // Optional
Command buffers are executed by submitting them on one of the device queues, like the graphics and presentation queues we retrieved. Each command pool can only allocate command buffers that are submitted on a single type of queue. We're going to record commands for drawing, which is why we've chosen the graphics queue family.
命令buffer被执行by提交它们到一个设备队列-例如图形和呈现队列that我们检索过的。每个命令池只能分配一种类型queue的命令buffer。
There are two possible flags for command pools:
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
: Hint that command buffers are rerecorded with new commands very often (may change memory allocation behavior)VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
: Allow command buffers to be rerecorded individually, without this flag they all have to be reset together
命令池有2个候选的标志:
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT
:命令buffer常常记录新的命令(可能改变内存分配行为)。VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
:允许命令缓存独立记录,如果没有这个标志,它们就必须一起重置。
We will only record the command buffers at the beginning of the program and then execute them many times in the main loop, so we're not going to use either of these flags.
我们只会在程序开始时记录命令,然后在主循环中执行很多次,所以我们不会用这2个标志。
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); }
Finish creating the command pool using the vkCreateCommandPool
function. It doesn't have any special parameters. Commands will be used throughout the program to draw things on the screen, so the pool should only be destroyed at the end:
首先用vkCreateCommandPool
创建命令池。
void cleanup() { vkDestroyCommandPool(device, commandPool, nullptr); ... }
Command buffer allocation 分配命令buffer
We can now start allocating command buffers and recording drawing commands in them. Because one of the drawing commands involves binding the right VkFramebuffer
, we'll actually have to record a command buffer for every image in the swap chain once again. To that end, create a list of VkCommandBuffer
objects as a class member. Command buffers will be automatically freed when their command pool is destroyed, so we don't need an explicit cleanup.
我们现在可以分配命令buffer,向其中记录绘制命令了。因为有个绘制命令涉及绑定到正确的VkFramebuffer
,我们实际上要记录一个命令buffer for交换链的每个image。为此,创建VkCommandBuffer
对象数组作为类成员。命令buffer会自动释放when它们的命令池被销毁,所以我们不需显示地清理它们。
std::vector<VkCommandBuffer> commandBuffers;
We'll now start working on a createCommandBuffers
function that allocates and records the commands for each swap chain image.
我们现在开始编写createCommandBuffers
函数that分配和记录命令for交换链的每个image。
1 void initVulkan() { 2 createInstance(); 3 setupDebugCallback(); 4 createSurface(); 5 pickPhysicalDevice(); 6 createLogicalDevice(); 7 createSwapChain(); 8 createImageViews(); 9 createRenderPass(); 10 createGraphicsPipeline(); 11 createFramebuffers(); 12 createCommandPool(); 13 createCommandBuffers(); 14 } 15 16 ... 17 18 void createCommandBuffers() { 19 commandBuffers.resize(swapChainFramebuffers.size()); 20 }
Command buffers are allocated with the vkAllocateCommandBuffers
function, which takes a VkCommandBufferAllocateInfo
struct as parameter that specifies the command pool and number of buffers to allocate:
命令buffer用vkAllocateCommandBuffers
函数分配,它接收一个VkCommandBufferAllocateInfo
结构体作为参数that指定命令池和要分配的buffer的数量:
VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { throw std::runtime_error("failed to allocate command buffers!"); }
The level
parameter specifies if the allocated command buffers are primary or secondary command buffers.
VK_COMMAND_BUFFER_LEVEL_PRIMARY
: Can be submitted to a queue for execution, but cannot be called from other command buffers.VK_COMMAND_BUFFER_LEVEL_SECONDARY
: Cannot be submitted directly, but can be called from primary command buffers.
参数level
指定了分配的命令buffer是一级还是二级命令buffer。
VK_COMMAND_BUFFER_LEVEL_PRIMARY
:可被提交到queue去执行,但不能被其他命令buffer调用。VK_COMMAND_BUFFER_LEVEL_SECONDARY
:不能被直接提交,但可以被一级命令buffer调用。
We won't make use of the secondary command buffer functionality here, but you can imagine that it's helpful to reuse common operations from primary command buffers.
Starting command buffer recording 开始在命令buffer里记录命令
We begin recording a command buffer by calling vkBeginCommandBuffer
with a small VkCommandBufferBeginInfo
structure as argument that specifies some details about the usage of this specific command buffer.
我们开始记录命令buffer by调用vkBeginCommandBuffer
with一个小的VkCommandBufferBeginInfo
structure结构体作为参数that指定这个命令buffer的用法的细节。
for (size_t i = 0; i < commandBuffers.size(); i++) { VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; beginInfo.pInheritanceInfo = nullptr; // Optional if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin recording command buffer!"); } }
The flags
parameter specifies how we're going to use the command buffer. The following values are available:
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
: The command buffer will be rerecorded right after executing it once.VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT
: This is a secondary command buffer that will be entirely within a single render pass.VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT
: The command buffer can be resubmitted while it is also already pending execution.
参数flags
指定了我们要如何使用这个命令buffer。下述值可选:
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
:命令buffer会在执行一次后立即再次记录。VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT
:这是二级命令buffer,其完全处于一个render pass内。VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT
:这个命令缓存可以被重新提交,同时它已经准备好执行了。
We have used the last flag because we may already be scheduling the drawing commands for the next frame while the last frame is not finished yet. The pInheritanceInfo
parameter is only relevant for secondary command buffers. It specifies which state to inherit from the calling primary command buffers.
我们用了最后一个标志,因为我们可能已经在安排下一帧的绘制命令,同时最新的一帧还没完成。参数pInheritanceInfo
只与二级命令buffer有关。它指定了从一级命令buffer继承哪个状态。
If the command buffer was already recorded once, then a call to vkBeginCommandBuffer
will implicitly reset it. It's not possible to append commands to a buffer at a later time.
如果命令buffer已经记录过一次了,那么调用vkBeginCommandBuffer
会隐式地重置它。之后扩展命令到buffer里是无法做到的。
Starting a render pass 启动一个render pass
Drawing starts by beginning the render pass with vkCmdBeginRenderPass
. The render pass is configured using some parameters in a VkRenderPassBeginInfo
struct.
绘制开始by调用vkCmdBeginRenderPass
。Render pass用VkRenderPassBeginInfo
结构体的一些参数来配置。
VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[i];
The first parameters are the render pass itself and the attachments to bind. We created a framebuffer for each swap chain image that specifies it as color attachment.
第一个参数是render pass自己和要绑定的附件。我们为交换链的每个image创建了一个帧缓存that指定它为颜色附件。
renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = swapChainExtent;
The next two parameters define the size of the render area. The render area defines where shader loads and stores will take place. The pixels outside this region will have undefined values. It should match the size of the attachments for best performance.
接下来的2个参数定义渲染区域的大小。渲染区域定义了shader加载和保存的位置。位于区域外的像素的值是未定义的。它应当与附件的大小匹配for最佳性能。
VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor;
The last two parameters define the clear values to use for VK_ATTACHMENT_LOAD_OP_CLEAR
, which we used as load operation for the color attachment. I've defined the clear color to simply be black with 100% opacity.
最后2个参数定义清空值for VK_ATTACHMENT_LOAD_OP_CLEAR
,which我们用作load操作for颜色附件。我已经定义了清空颜色to简单的黑色with 100%不透明度。
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
The render pass can now begin. All of the functions that record commands can be recognized by their vkCmd
prefix. They all return void
, so there will be no error handling until we've finished recording.
现在可以开始render pass了。记录命令的所有函数都带有vkCmd
前缀。它们都返回void
,所以在我们结束记录前,不会有任何错误处理。
The first parameter for every command is always the command buffer to record the command to. The second parameter specifies the details of the render pass we've just provided. The final parameter controls how the drawing commands within the render pass will be provided. It can have one of two values:
VK_SUBPASS_CONTENTS_INLINE
: The render pass commands will be embedded in the primary command buffer itself and no secondary command buffers will be executed.VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
: The render pass commands will be executed from secondary command buffers.
每个命令的第一个参数总是用于记录命令的buffer。第二个参数指定我们提供的render pass的细节。最后一个参数控制render pass的绘制命令如何被提供。它可能有下述2个值之一:
VK_SUBPASS_CONTENTS_INLINE
:render pass命令会被嵌入一级命令buffer本身,不会有二级命令被执行。VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
:render pass命令会被二级命令buffer执行。
We will not be using secondary command buffers, so we'll go with the first option.
我们不会使用二级命令buffer,所以我们选第一个。
Basic drawing commands 基础绘制命令
We can now bind the graphics pipeline:
我们现在可以绑定图形管道:
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
The second parameter specifies if the pipeline object is a graphics or compute pipeline. We've now told Vulkan which operations to execute in the graphics pipeline and which attachment to use in the fragment shader, so all that remains is telling it to draw the triangle:
第二个参数指定管道对象是图形or计算管道。我们现在告诉了Vulkan在图形管道执行哪些操作,在Fragment shader中使用哪些附件,所以剩下的就是告诉它to绘制三角形:
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
The actual vkCmdDraw
function is a bit anticlimactic, but it's so simple because of all the information we specified in advance. It has the following parameters, aside from the command buffer:
vertexCount
: Even though we don't have a vertex buffer, we technically still have 3 vertices to draw.instanceCount
: Used for instanced rendering, use1
if you're not doing that.firstVertex
: Used as an offset into the vertex buffer, defines the lowest value ofgl_VertexIndex
.firstInstance
: Used as an offset for instanced rendering, defines the lowest value ofgl_InstanceIndex
.
函数vkCmdDraw
有点虎头蛇尾的,但是它如此简单-因为我们提前指定了所有的信息。它的参数,除了命令缓存外,有:
vertexCount
:即使没有顶点buffer,我们技术上仍旧有3个顶点要画。instanceCount
:用于instanced渲染,如果不用,填入1
。firstVertex
:顶点buffer的偏移量,它定义了gl_VertexIndex
的最小值。firstInstance
:用于instanced渲染的偏移量,定义了gl_InstanceIndex
的最小值。
Finishing up 马上完工
The render pass can now be ended:
Render pass现在可以结束了:
vkCmdEndRenderPass(commandBuffers[i]);
And we've finished recording the command buffer:
我们已经完成了记录命令buffer的任务:
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); }
In the next chapter we'll write the code for the main loop, which will acquire an image from the swap chain, execute the right command buffer and return the finished image to the swap chain.
下一章我们将写主循环的代码,which要从交换链请求一个image,执行正确的命令buffer,返回完成的image到交换链。
C++ code / Vertex shader / Fragment shader
微信扫码,自愿捐赠。天涯同道,共谱新篇。
微信捐赠不显示捐赠者个人信息,如需要,请注明联系方式。 |