vulakn教程--Drawing a Triangle--Draw--CommandBuffer
发表于2016-12-05
原文地址 : vulkan-tutorial
Command buffers
在Vulkan中,像绘画命令、内存转换等操作并不是直接通过方法调用去完成的,而是需要把所有的操作放在Command Buffer 里。这样的一个好处就是:那些已设置好的具有难度的绘图工作都可以在多线程的情况下提前完成。
Command pools
Command pools 管理Command buffer 的内存而且Command buffer 从Command pool中被创建。所以我们必须先来创建Command pool:
VDeleter commandPool {device, vkDestroyCommandPool};
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
poolInfo.flags = 0; // Optional
command buffer 需要提交到提交到队列中等待执行,像我们已经得到的图形队列(graphics)和显示队列(presentation)。从一个Command pool 产生所有command buffers 只能对应一种特定的队列。因为我们想使用绘图命令,所以选择graphics 队列 。
flags的取值只有两个:
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT: Command buffer 比较短命,可能会在一个相对较短
的时间内被重置或释放,主要用于控制pool中内存的分配行为。
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT: commad buffer是否可以被分别重置,如果无此参数,pool中所有的command buffer都将被重置。
我们只是在程序的开始时记录Command buffer ,然后在main loop 中调用多次,所以不需要flags参数。
创建 Command Pool:
if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}
Command buffer allocation
让我们开始创建Command buffer并将绘画命令写入其中吧。因为绘画命令涉及绑定到正确的VkFrameBuffer,所以我们要为Swap Chain里的每一个image 创建一个Command buffer:
std::vector commandBuffers;
commandBuffers.resize(swapChainFramebuffers.size());
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();
// 创建Command buffer
if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("failed to allocate command buffers!");
}
level 字段限定了command buffer 是主要的还是次要的,它有如下取值:
VK_COMMAND_BUFFER_LEVEL_PRIMARY: 可以提交到队列中执行,但不能从其他command buffer 中调用。
VK_COMMAND_BUFFER_LEVEL_SECONDARY: 不能直接提交到队列,但可以从主command buffer 中调用。
此外CommandBuffer 的清理工作和之前的对其他对象的清理工作不同,使用:vkFreeCommandBuffers(),它接收一个Command Pool和一个CommandBuffer数组。
Starting command buffer recording
我们通过vkBeginCommandBuffer来开始记录 Command buffer ,并用VkCommandBufferBeginInfo来描述command 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
vkBeginCommandBuffer(commandBuffers[i], &beginInfo);
}
flags 定义我们该如何使用command buffer ,可能取值:
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT: 只能被提交一次,之后可能被重置。
VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT: 整个次command buffer 将会在reder pass中,主command buffer 将忽略此值。
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT: command buffer 在等待执行时可以被重复提交
在这里我们使用_SIMUTANEOUS_USE_BIT,因为很有可能在上一个帧(frame)尚未画完,下一个帧的绘画请求就已经提交了。pInheritanceInfo表示次command buffer 从主command buffer 继承过来的状态(state)。
Starting a render pass
Render pass 通过vkCmdBeginRenderPass 开始后绘画才能开始,我们需要VkRenderPassBeginInfo来描述Render Pass 的一些细节:
VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];// a color attachment
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = swapChainExtent;
限定render 的区域,它定义了着色器(Shader)加载(load)和存储(store)的发生的区域,区域外属于未定义部分,为了获得更好的性能,render区域应和attachment的尺寸一致。
VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
用黑色清空frame buffer ,对应我们之前设置的VK_ATTACHMENT_LOAD_OP_CLEAR参数。
开始 Render Pass:
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
所有的记录命令都以vkCmd..前缀开始,第一个参数都是要将命令记录的位置,即CommandBuffer。
第三个参数控制带有Render Pass的绘画命令(drawing command)如何被处理,取值:
VK_SUBPASS_CONTENTS_INLINE: Render pass commands 嵌入到 command buffer中,secondary command buffers 将不会被执行。
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS: Render pass commands 将从 secondary command buffers 中被执行。
我们没有使用次command buffer(secondary command buffers) ,所以选择第一种。
Basic drawing commands and Finishing up
将commnand buffer 和 pipeline 绑定:
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
VK_PIPELINE_BIND_POINT_GRAPHICS指定绑定何种类型的Pipeline,VK_PIPELINE_BIND_POINT_COMPUTE 属于计算类型。我们的Pipeline是图形类型,所以选VK_PIPELINE_BIND_POINT_GRAPHICS,他能控制如下命令:vkCmdDraw, vkCmdDrawIndexed, vkCmdDrawIndirect, and vkCmdDrawIndexedIndirect等。
画图函数:
void vkCmdDraw(
VkCommandBuffer commandBuffer,
uint32_t vertexCount, //顶点数量
uint32_t instanceCount, // 要画的instance数量,没有:置1
uint32_t firstVertex,// vertex buffer中第一个位置 和 vertex Shader 里gl_vertexIndex 相关。
uint32_t firstInstance);// 同firstVertex 类似。
开始画我们的三角形:
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
结束Render Pass:
vkCmdEndRenderPass(commandBuffers[i]);
结束记录 Command buffer:
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}
源码 : source code