操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Visual Studio 2017


诸如绘制和内存操作相关命令,在Vulkan中不是通过函数直接调用的。我们需要在命令缓冲区对象中记录我们期望的任何操作。这样做的优点是可以提前在多线程中完成所有绘制命令相关的装配工作,并在主线程循环结构中通知Vulkan执行具体的命令。

Command pools


我们在使用任何command buffers之前需要创建命令对象池command pool。Command pools管理用于存储缓冲区的内存,并从中分配命令缓冲区。添加新的类成员保存VkCommandPool:

VkCommandPool commandPool;

创建新的函数createCommandPool并在initVulkan函数创建完framebuffers后调用。

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

命令对象池创建仅仅需要两个参数:

QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);

VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
poolInfo.flags = ; // Optional

命令缓冲区通过将其提交到其中一个设备队列上来执行,如我们检索的graphics和presentation队列。每个命令对象池只能分配在单一类型的队列上提交的命令缓冲区,换句话说要分配的命令需要与队列类型一致。我们要记录绘制的命令,这就说明为什么要选择图形队列簇的原因。

有两个标志位用于command pools:

  • VK_COMMAND_POOL_CREATE_TRANSIENT_BIT: 提示命令缓冲区非常频繁的重新记录新命令(可能会改变内存分配行为)
  • VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT: 允许命令缓冲区单独重新记录,没有这个标志,所有的命令缓冲区都必须一起重置

我们仅仅在程序开始的时候记录命令缓冲区,并在主循环体main loop中多次执行,因此我们不会使用这些标志。

if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
throw std::runtime_error("failed to create command pool!");
}

通过vkCreateCommandPool函数完成command pool创建工作。它不需要任何特殊的参数设置。命令将被整个程序的生命周期使用以完成屏幕的绘制工作,所以对象池应该被在最后销毁:

void cleanup() {
vkDestroyCommandPool(device, commandPool, nullptr); ...
}

Command buffer allocation


现在我们开始分配命令缓冲区并通过它们记录绘制指令。因为其中一个绘图命令需要正确绑定VkFrameBuffer,我们实际上需要为每一个交换链中的图像记录一个命令缓冲区。最后创建一个VkCommandBuffer对象列表作为成员变量。命令缓冲区会在common pool销毁的时候自动释放系统资源,所以我们不需要明确编写cleanup逻辑。

std::vector<VkCommandBuffer> commandBuffers;

现在开始使用一个createCommandBuffers函数来分配和记录每一个交换链图像将要应用的命令。

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

命令缓冲区通过vkAllocateCommandBuffers函数分配,它需要VkCommandBufferAllocateInfo结构体作为参数,用以指定command pool和缓冲区将会分配的大小:

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!");
}

level参数指定分配的命令缓冲区的主从关系。

  • VK_COMMAND_BUFFER_LEVEL_PRIMARY: 可以提交到队列执行,但不能从其他的命令缓冲区调用。
  • VK_COMMAND_BUFFER_LEVEL_SECONDARY: 无法直接提交,但是可以从主命令缓冲区调用。

我们不会在这里使用辅助缓冲区功能,但是可以想像,对于复用主缓冲区的常用操作很有帮助。

Starting command buffer recording


通过vkBeginCommandBuffer来开启命令缓冲区的记录功能,该函数需要传递VkCommandBufferBeginInfo结构体作为参数,用以指定命令缓冲区在使用过程中的一些具体信息。

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 vkBeginCommandBuffer(commandBuffers[i], &beginInfo);
}

flags标志位参数用于指定如何使用命令缓冲区。可选的参数类型如下:

  • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT: 命令缓冲区将在执行一次后立即重新记录。
  • VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT: 这是一个辅助缓冲区,它限制在在一个渲染通道中。
  • VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT: 命令缓冲区也可以重新提交,同时它也在等待执行。

我们使用了最后一个标志,因为我们可能已经在下一帧的时候安排了绘制命令,而最后一帧尚未完成。pInheritanceInfo参数与辅助缓冲区相关。它指定从主命令缓冲区继承的状态。

如果命令缓冲区已经被记录一次,那么调用vkBeginCommandBuffer会隐式地重置它。否则将命令附加到缓冲区是不可能的。

Starting a render pass


绘制开始于调用vkCmdBeginRenderPass开启渲染通道。render pass使用VkRenderPassBeginInfo结构体填充配置信息作为调用时使用的参数。

VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = swapChainFramebuffers[i];

结构体第一个参数传递为绑定到对应附件的渲染通道本身。我们为每一个交换链的图像创建帧缓冲区,并指定为颜色附件。

renderPassInfo.renderArea.offset = {, };
renderPassInfo.renderArea.extent = swapChainExtent;

后两个参数定义了渲染区域的大小。渲染区域定义着色器加载和存储将要发生的位置。区域外的像素将具有未定的值。为了最佳的性能它的尺寸应该与附件匹配。

VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
renderPassInfo.clearValueCount = ;
renderPassInfo.pClearValues = &clearColor;

最后两个参数定义了用于 VK_ATTACHMENT_LOAD_OP_CLEAR 的清除值,我们将其用作颜色附件的加载操作。为了简化操作,我们定义了clear color为100%黑色。

vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

渲染通道现在可以启用。所有可以被记录的命令,被识别的前提是使用vkCmd前缀。它们全部返回void,所以在结束记录之前不会有任何错误处理。

对于每个命令,第一个参数总是记录该命令的命令缓冲区。第二个参数指定我们传递的渲染通道的具体信息。最后的参数控制如何提供render pass将要应用的绘制命令。它使用以下数值任意一个:

  • VK_SUBPASS_CONTENTS_INLINE: 渲染过程命令被嵌入在主命令缓冲区中,没有辅助缓冲区执行。
  • VK_SUBPASS_CONTENTS_SECONDARY_COOMAND_BUFFERS: 渲染通道命令将会从辅助命令缓冲区执行。

我们不会使用辅助命令缓冲区,所以我们选择第一个。

Basic drawing commands


现在我们绑定图形管线:

vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

第二个参数指定具体管线类型,graphics or compute pipeline。我们告诉Vulkan在图形管线中每一个操作如何执行及哪个附件将会在片段着色器中使用,所以剩下的就是告诉它绘制三角形。

vkCmdDraw(commandBuffers[i], , , , );

实际的vkCmdDraw函数有点与字面意思不一致,它是如此简单,仅因为我们提前指定所有渲染相关的信息。它有如下的参数需要指定,除了命令缓冲区:

  • vertexCount: 即使我们没有顶点缓冲区,但是我们仍然有3个定点需要绘制。
  • instanceCount: 用于instanced 渲染,如果没有使用请填1。
  • firstVertex: 作为顶点缓冲区的偏移量,定义gl_VertexIndex的最小值。
  • firstInstance: 作为instanced 渲染的偏移量,定义了gl_InstanceIndex的最小值。

Finishing up


render pass执行完绘制,可以结束渲染作业:

vkCmdEndRenderPass(commandBuffers[i]);

并停止记录命令缓冲区的工作:

if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!");
}

在下一章节我们会尝试在main loop中编写代码,用于从交换链中获取图像,执行命令缓冲区的命令,再将渲染后的图像返还给交换链。

项目代码 GitHub地址。

Vulkan Tutorial 16 Command buffers的更多相关文章

  1. [译]Vulkan教程(18)命令buffers

    [译]Vulkan教程(18)命令buffers Command buffers 命令buffer Commands in Vulkan, like drawing operations and me ...

  2. [译]Vulkan教程(16)图形管道基础之总结

    [译]Vulkan教程(16)图形管道基础之总结 Conclusion 总结 We can now combine all of the structures and objects from the ...

  3. Vulkan Tutorial 17 Rendering and presentation

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Setup 这一章节会把之前的所有内容进行整合.我们将会编写drawFrame函数, ...

  4. Vulkan Tutorial 18 重构交换链

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 现在我们已经成功的在屏幕上绘制出三角形,但是在某些情况下, ...

  5. Vulkan Tutorial 20 Vertex buffer creation

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 在Vulkan中,缓冲区是内存的一块区域,该区域用于向显卡 ...

  6. Vulkan Tutorial 25 Images

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 到目前为止,几何图形使用每个顶点颜色进行着色处理,这是一个 ...

  7. Vulkan Tutorial 29 Loading models

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 应用程序现在已经可以渲染纹理3D模型,但是 vertice ...

  8. Vulkan Tutorial 开发环境搭建之Windows

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 相信很多人在开始学习Vulkan开发的起始阶段都会在开发环境的配置上下一些功夫,那么 ...

  9. Vulkan Tutorial 02 编写Vulkan应用程序框架原型

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 General structure 在上一节中,我们创建了一个正确配置.可运行的的V ...

随机推荐

  1. eclipse中AndroidA工程依赖B工程设置

    假设library为B工程,而SlideMenuTest为A工程,且SlideMenuTest需要依赖library工程(减少jar包形式的修改麻烦). 需要简单的设置即可. 1.B工程设置为libr ...

  2. CSS表单属性

    一般来说,表单在一个页面中是必不可少的,下面是我对表单的知识总结: 依次要说的是表单元素.表单属性.以及表单提交(js知识) 1,表单元素: <form action="提交的位置 / ...

  3. bzoj4819 [Sdoi2017]新生舞会

    Description 学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴.有n个男生和n个女生参加舞会买一个男生和一个女生一起跳舞,互为舞伴.Cathy收集了这些同学之间的 ...

  4. uoj#179 线性规划

    这是一道模板题. 本题中你需要求解一个标准型线性规划: 有nn个实数变量x1,x2,⋯,xnx1,x2,⋯,xn和mm条约束,其中第ii条约束形如∑nj=1aijxj≤bi∑j=1naijxj≤bi. ...

  5. GTK简单了解记录

    GTK+http://zh.wikipedia.org/wiki/GTK%2B#.E5.9B.BE.E5.BD.A2.E6.97.A0.E5.85.B3.E4.BB.A3.E7.A0.81 GTK+最 ...

  6. python2与python3的不兼容_urllib2

    网页下载器:将URL对应的网页以HTML下载到本地,用于后续分析 常见网页下载器:Python官方基础模块:urllib2 第三方功能包:requests python 3.x中urllib库和uri ...

  7. Java提高(一)---- HashMap

    阅读博客 1, java提高篇(二三)-----HashMap 这一篇由chenssy发表于2014年1月,是根据JDK1.6的源码讲的. 2,Java类集框架之HashMap(JDK1.8)源码剖析 ...

  8. angularjs ng-class

    ng-class指令可以设置一个键值对,用于决定是否添加一个特定的类名,键为class名,值为bool类型表示是否添加该类名 <style> .red { color: red; } .g ...

  9. HTML标签元素分类(HTML基础知识)

    HTML标签元素分类 一.按照块级元素还是行内元素分类 块级元素(block-level)和行内元素(inline-level,也叫作"内联"元素). a.块级元素(独占一行) 块 ...

  10. CSS预编译器:Sass(入门),更快的前端开发

    SASs是由美国注册会计师协会(AICPA)下属审计准则委员会(ASB)发布,是为了便于注册会计师执行和落实一般公认审计准则(GAAS). Sass 扩展了 CSS3,增加了规则.变量.混入.选择器. ...