[译]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帧缓存创建后。

 void initVulkan() {
createInstance();
setupDebugCallback();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createFramebuffers();
createCommandPool();
} ... void createCommandPool() { }

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 = ; // 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。

 void initVulkan() {
createInstance();
setupDebugCallback();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createFramebuffers();
createCommandPool();
createCommandBuffers();
} ... void createCommandBuffers() {
commandBuffers.resize(swapChainFramebuffers.size());
}

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 VkCommandBufferBeginInfostructure as argument that specifies some details about the usage of this specific command buffer.

我们开始记录命令buffer by调用vkBeginCommandBuffer with一个小的VkCommandBufferBeginInfostructure结构体作为参数that指定这个命令buffer的用法的细节。

for (size_t i = ; 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 = {, };
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 = ;
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], , , , );

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, use 1 if you're not doing that.
  • firstVertex: Used as an offset into the vertex buffer, defines the lowest value of gl_VertexIndex.
  • firstInstance: Used as an offset for instanced rendering, defines the lowest value of gl_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

[译]Vulkan教程(18)命令buffers的更多相关文章

  1. [译]Vulkan教程(27)Image

    [译]Vulkan教程(27)Image Images Introduction 入门 The geometry has been colored using per-vertex colors so ...

  2. [译]Vulkan教程(26)描述符池和set

    [译]Vulkan教程(26)描述符池和set Descriptor pool and sets 描述符池和set Introduction 入门 The descriptor layout from ...

  3. [译]Vulkan教程(25)描述符布局和buffer

    [译]Vulkan教程(25)描述符布局和buffer Descriptor layout and buffer 描述符布局和buffer Introduction 入门 We're now able ...

  4. [译]Vulkan教程(24)索引buffer

    [译]Vulkan教程(24)索引buffer Index buffer 索引buffer Introduction 入门 The 3D meshes you'll be rendering in a ...

  5. [译]Vulkan教程(23)暂存buffer

    [译]Vulkan教程(23)暂存buffer Staging buffer 暂存buffer Introduction 入门 The vertex buffer we have right now ...

  6. [译]Vulkan教程(22)创建顶点buffer

    [译]Vulkan教程(22)创建顶点buffer Vertex buffer creation 创建顶点buffer Introduction 入门 Buffers in Vulkan are re ...

  7. [译]Vulkan教程(20)重建交换链

    [译]Vulkan教程(20)重建交换链 Swap chain recreation 重建交换链 Introduction 入门 The application we have now success ...

  8. [译]Vulkan教程(19)渲染和呈现

    [译]Vulkan教程(19)渲染和呈现 Rendering and presentation 渲染和呈现 Setup 设置 This is the chapter where everything ...

  9. [译]Vulkan教程(13)图形管道基础之Shader模块

    [译]Vulkan教程(13)图形管道基础之Shader模块 Shader modules Unlike earlier APIs, shader code in Vulkan has to be s ...

随机推荐

  1. unittest自动化测试框架

    目录 框架的概念 Unittest单元测试框架 常用的assert语句 unittest创建测试代码的方式: unittest构建测试套件(测试用例集合): unittest忽略测试用例: 运行测试集 ...

  2. 【Java Web开发学习】Spring MVC 开始配置

    Spring MVC 开始配置 转载:http://www.cnblogs.com/yangchongxing/p/8871370.htm 学习搭建最简单的Spring MVC框架. ======== ...

  3. webpack学习_管理输出(管理资源插件)

    管理输出步骤 Step1:在src新建文件print.js添加逻辑 Step2:在src/index.js import 引用新添加的逻辑 Step3:更新dist/index.html文件,修改引入 ...

  4. django基础之day04知识点----查询相关

    from django.test import TestCase # Create your tests here. ''' 当你想单独测试django中某一个py文件时,你需要手动配置测试文件 在m ...

  5. 《Java基础知识》Java断言

    断言:也就是所谓的assertion,是jdk1.4后加入的新功能. 它主要使用在代码开发和测试时期,用于对某些关键数据的判断,如果这个关键数据不是你程序所预期的数据,程序就提出警告或退出. 当软件正 ...

  6. 【Python进阶】来谈谈几个常用的内置函数

    匿名函数(lambda表达式) 在Python中,函数可以算的上是“一等公民”了,我们先回顾下函数的优点: 减少代码重复量 模块化代码 但是我们有没有想过,如果我们需要一个函数,比较简短,而且只需要使 ...

  7. 使用VS code 创建 Azure Functions,从blob触发,解析,发送至Service Bus

    更多内容,关注公众号:来学云计算 场景: 某设备定时于每天23:00左右将一天的运行日志.devicelogtxt上传到Azure Blob,期待Blob文件上传后, 自动通过Azure Functi ...

  8. CSP-S 2019 游记

    目录 CSP-S 2019 游记 DAY -1 Day 0 Day 1 Day 2 后记 CSP-S 2019 游记 机房段子: zr(老师):yyx我看你最近不错哦(此人外号拳皇 yyx:运气好运气 ...

  9. Redis 的常用命令

    Redis 的数据类型 一些命令需要结合 redis 的数据类型来说. Redis 4.0 之前有5种数据类型,分别是:字符串(string).散列(hash).列表(list).集合(set).有序 ...

  10. 消息队列MQ简介

    项目中要用到RabbitMQ,领导让我先了解一下.在之前的公司中,用到过消息队列MQ,阿里的那款RocketMQ,当时公司也做了简单的技术分享,自己也看了一些博客.自己在有道云笔记上,做了一些整理,但 ...