创建顶点缓存
在SceneWidget.h中添加创建顶点缓存的函数bool CreateVertexBuffer(),然后在SceneWidget.cpp中实现它。
在函数中首先要计算要生成的顶点缓存的大小,顶点缓存的大小与顶点的顶点、个数有关系,一般为一个顶点的属性占用的字节数乘以顶点的个数。
先定义顶点的数据结构,由于现在只是展示简单的场景,绘制一个多边形,或者说最简单的三角形。那么在SceneWidget.h中定义:
其中Vertex数据结构表示顶点的属性,属性包括:顶点的位置和颜色两种。其中位置是由Vector3数据结构表示,包含float类型的xyz三个分量。Vector4结构中包含了一个Vector3的对象和一个代表颜色的alpha通道的变量。
为了少写一点代码,这里只做顶点绘制,索引绘制,以后再说。
继续在SceneWidget.h中添加一个成员变量:std::vector<Vertex> m_vertexs;表示顶点列表,最终要绘制的就是这个里面的数据。
那么顶点缓存的大小就是:
VkDeviceSize bufferSize = sizeof(Vertex)*m_vertexs.size()
这里顶点数据具体是什么,先不关心,等到必需用时,再填值。
声明一个顶点缓存,VkBuffer vertexBuffer;和对应的显存VkDeviceMemory vertexBufferMemory。
对于生成buffer代码,由于后续生成纹理、顶点索引绘制等,都需要生成buffer,所以将提炼成bool CreateBuffer()函数,其中的参数为要创建Buffer的大小、buffer的使用方式、buffer的内存布局、生成的Buffer的引用和对应的BufferMemory的引用。
所以在SceneWidget.h中添加一个bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
VkBuffer& buffer, VkDeviceMemory& bufferMemory)
在SceneWidget.cpp中实现这个函数:
还是老样子,先写buffer的Create函数,即:
VkResult result = vkCreateBuffer() 其参数:
第一个参数:m_device
第二个参数:是VkBufferCreateInfo结构的指针,这里补一个定义,VkBufferCreateInfo creatInfo = {},填入&ceateInfo
第三个参数:nullptr
第四个参数:是传入的buffer的引用。
判断返回值,是否create成功。
在vkCreateBuffer函数的上面补VkBufferCreateInfo creatInfo = {}且createInfo的属性赋值 :
固定的写法
createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
保持默认
createInfo.pNext = nullptr;
createInfo.flags = 0;
由参数传入,直接赋值
createInfo.size = size;
直接赋值
createInfo.usage = usage;
保持默认,也就是不共享。对于这个demo,赋值共享和不共享,没有区别。但是根据vkspec中的说明,当值不为VK_SHARING_MODE_CONCURRENT的值时,忽略createInfo.pQueueFamilyIndices值。
createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
忽略这个参数。
createInfo.queueFamilyIndexCount = 0;
忽略这个参数。
createInfo.pQueueFamilyIndices = 0;
和创建Image一样,buffer创建完成后,需要再申请资源,并与生成的Buffer进行绑定。
这里有两种绘制的方法:
一种是立即绘制,就是说通过在cpu端进行顶点填充,然后立即绘制,显卡读取内存中的数据时行渲染。详细过程可参考《vulkan编程指南》23节
一种是在cpu端先将数据进行缓存再通过数据拷贝,将数据拷贝到显存中,再进行数据的渲染。详细过程可参考《vulkan编程指南》24节
在这里使用第二种方式,显卡读取显存的速度要高于读取cpu内存的速度。
之前在创建Image时,已经操作过了,这里不再重复。出现了相同的代码,可以考虑提炼成函数。
继续CreateVertexBuffer函数,将第一次调用CreateBuffer函数,这次只是将数据从内存中读取到cpu的缓存中
其参数:
第一个参数:计算好的BufferSize,
第二个参数:VkBufferUsageFlags的值,这buffer是要用做存储cpu端的数据的,所以要做数据源,即用VK_BUFFER_USAGE_TRANSFER_SRC_BIT标志
第三个参数:VkMemoryPropertyFlags的值,根据vkspec中对每个枚举值的解释,可以使用VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,要与cpu可以访问这块内存,且还要使用VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,不需要额外的操作,就可以使cpu能直接在这块内存上进行写入。所以最终是VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT。
第四个参数:要创建的buffer对象vertexBuffer
第五个参数:要生成的memory对象vertexBufferMemory
到了这里,实际上只是申请了内存,还没有向里写数据。
下面向里写数据时,分两步,先通过vkMapMemory,将内存映射到一个void*的指针上。然后再将顶点数据写入这个指针。最后再将这个映射释放就可以了。
映射:
现在数据已经保存到了源bufer中,还需要一个最终的buffer,然后将数据再拷贝到最终的buffer里。
再调用CreateBuffer函数,第二次调用时,参数会有不同,其参数为:
第一个参数:还是bufferSize.
第二个参数:VkBufferUsageFlags的值,这次是目的buffer,取:VK_BUFFER_USAGE_TRANSFER_DST_BIT以及这个buffer的用途,即buffer内的数据是用来做为顶点的VK_BUFFER_USAGE_VERTEX_BUFFER_BIT。所以最终:
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
第三个参数:与上次的调用一样,VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
第四个参数:需要补成员变量VkBuffer m_vertexBuffer来记录生成buffer,以及对应的VkDeviceMemory m_vertexMemory。则这里的值就是m_vertexBuffer
第五个参数:m_vertexMemory。
接下来就要把vertexBuffer里的数据拷贝到m_vertexBuffer去就可以了。
调用vkCmdCopyBuffer函数,进行拷贝。以vkCmd开头的函数,一般参数中都有一个对应的comandBuffer。所以要先获取或生成一个commandBuffer。然后重置缓冲命令,重置后,就是拷贝操作。拷贝完成后,还要调用vkEndCommandBuffer结束指令的记录,并提交队列。
最后释放commandBuffer对象。
所以在SceneWidget.h中新增加两个函数,一个是VkCommandBuffer BeginCommands(),在函数做一些commandBuffer的生成操作,并返回这个生成的对象。
一个void EndCommands(VkCommandBuffer commandBuffer)对输入的commandBuffer进行提交和清理工作。
先完成BeginCommands函数,VkCommandBuffer是通过vkAllocateCommandBuffers函数来分配得到的,其参数:
第一个参数:m_device
第二个参数:VkCommandBufferAllocateInfo的数据结构,在函数上方定义一个VkCommandBufferAllocateInfo allocateInfo = {};传入&allocateInfo。
第三个参数:是VkCommandBuffer对象的引用,所以在VkCommandBuffer的上方再定义一个变量,VkCommandBuffe commandBuffer,然后传&commandBuffer。
判断其返回值。
现在补齐allocateInfo的属性值:
固定写法
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
保持默认。
allocateInfo.pNext = nullptr;
之前创建好的。
allocateInfo.commandPool = m_commandPool;
提交到队列进行执行。
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
只申请了一个。
allocateInfo.commandBufferCount = 1;
调用vkBeginCommandBuffer进行指令缓冲的记录操作。同样需要判断返回值。
其参数:
第一个参数:为上面申请的commandBuffer
第二个参数:VkCommandBufferBeginInfo的数据结构,补一个变量:VkCommandBufferBeginInfo beginInfo = {};传入引用。
同样补齐beginInfo的属性:
固定写法
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
默认
beginInfo.pNext = nullptr;
VkCommandBufferUsageFlagBits的标志位,从每个标志位的解释来看,使用第一个枚举值,即:执行一次后,可用来记录新的指令
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
这个属性只有在allocateInfo.level的值为VK_COMMAND_BUFFER_LEVEL_SECONDARY的时候使用。
beginInfo.pInheritanceInfo = nullptr;
然后函数返回生成的commandBuffer。
在CreateVertexBuffer中调用BeginCommands()。
VkCommandBuffer commandBuffer = BeginCommands();
这样取到了commandBuffer。
执行vkCmdCopyBuffer函数,其参数:
第一个参数:传入commandBuffer。
第二个参数:传入源buffer,即:vertexBuffer
第三个参数:传入目的buffer,即:m_vertexBuffer
第五个参数:传入的是VkBufferCopy的指针,所以补一个变量VkBufferCopy copyRegion = {};
第四个参数:是VkBufferCopy数据结构的数量,这里应该是一个。
同样,补齐copyRegion的属性
读取数据时源buffer中的偏移
copyRegion.srcOffset = 0;
写数据时目的buffer中的偏移
copyRegion.dstOffset = 0;
拷贝的大小
copyRegion.size = bufferSize;
在函数的最后调用EndCommands(VkCommandBuffer commandBuffer)。
void EndCommands(VkCommandBuffer commandBuffer)函数的实现:
在这个函数,先结束写入操作,调用vkEndCommandBuffer(commandBuffer);然后需要提交队列vkQueueSubmit()。其参数:
第一个参数:要提交的队列,这里是m_graphicsQueue,进行绘制。
第二个参数:就提交一个队列,所以这里为1。
第三个参数:为VkSubmitInfo的数据结构,所以补一个变量:VkSubmitInfo submitInfo = {};,传入引用。
第四个参数:同步控制,可以定义一个VkFence,也可设置VK_NULL_HANDLE,在这个函数的后面调用vkQueueWaitIdle函数进行同步。这里,使用后一种方法。
补齐submitInfo的属性
固定写法
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
保持默认
submitInfo.pNext = nullptr;
这里只有一个绘制用的队列,不需要信号,所以保持默认
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = nullptr;
submitInfo.pWaitDstStageMask = nullptr;
只有一个提交的commandBuffer
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
不使用信号
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = nullptr;
提交完成后,使用vkQueueWaitIdle等待绘制完成。
最后清理commandBuffer。调用vkFreeCommandBuffers
再清理之前申请的cpu端的缓存以及对应的memory:
在SceneWidget::Init函数中加入调用,并在SceneWidget::UnInit函数加入清理函数,如上,只是将buffer的变量名换成m_vertexBuffer和m_vertexMemory。
运行:
出错了,从错误信息上看,应该是顶点的size不能为0。
现在为顶点数据赋值:
需要在CreateVertexBuffer调用的前面加一个void SetPoint()的函数,给坐标赋值。
然后再运行看下效果:
没有错误了:)