vulkan学习笔记十四

创建顶点缓存

在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()的函数,给坐标赋值。

然后再运行看下效果:

没有错误了:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值