VulkanTutorial(12·recreation swap chain,Vertex buffers)
Swap chain recreation
我们要优化程序,因为window surface(窗口大小)可能会发生变化,导致swapchain不再兼容,当这个事件发生时,我们应该重新创建swapchain
实例
我们写新的recreateSwapChain和cleanupSwapChain函数
- 它应该包括所有依赖于交换链或窗口大小的对象的创建函数
- 并且还应该包括vkDeviceWaitIdle,因为不应当资源正在被使用时执行recreateSwapChain
- 为了简单起见,我们不重新创建渲染通道,
- 我们在chooseSwapExtent选择交换链分辨率函数中,已经通过glfwGetFramebufferSize查询新的窗口分辨率,以确保交换链图像具有正确的大小
- 这里的glfwGetFramebufferSize是为了保证只有在窗口有有效尺寸时才继续
- 对于cleanupSwapChain需要清理Framebuffer,ImageView,SwapChain
void recreateSwapChain() {int width = 0, height = 0;glfwGetFramebufferSize(window, &width, &height);while (width == 0 || height == 0) {glfwGetFramebufferSize(window, &width, &height);glfwWaitEvents();}vkDeviceWaitIdle(device);cleanupSwapChain();createSwapChain();createImageViews();createFramebuffers();
}
调用
现在我们只需要弄清楚什么时候需要重新创建交换链(即什么时候调整了窗口),drawFrame中的vkAcquireNextImageKHR and vkQueuePresentKHR函数,可以告诉我们交换链不再匹配,以下是函数VkResult返回值:
- VK_ERROR_OUT_OF_DATE_KHR交换链已与表面不兼容,无法再用于渲染。通常发生在窗口调整大小之后
- VK_SUBOPTIMAL_KHR:交换链仍然可以用于成功地呈现给曲面,但曲面属性不再完全匹配。
我们应该在drawFrame函数中调用,且在vkWaitForFences和vkAcquireNextImageKHR之后,vkResetFences之前
(如果在vkResetFences之后,fence先被重置,可是如果==VK_ERROR_OUT_OF_DATE_KHR,就会调用recreateSwapChain()并return,这时由于Reset了,vkWaitForFences永远不会通过,造成deadlock)
(反而在vkResetFences之前,当return,fence没有被重置,再次进入vkWaitForFences()后它会成功通过)
优化
尽管许多驱动程序和平台在窗口调整大小后会自动触发VK_ERROR_OUT_OF_DATE_KHR,但并不保证会发生这种情况
因此要添加一些额外的代码来显式地处理大小调整,对于vkQueuePresentKHR函数之后位置调用使用glfwSetFramebufferSizeCallback设置回调,对于callback函数需要为static函数,因为GLFW无法处理成员函数
那既然如此它如何修改Application中的framebufferResized成员呢?通过glfwGetWindowUserPointer获取用户指针所在的窗口,再reinterpret_cast<HelloTriangleApplication*>强制转换为类,即可修改类成员
现在就可以调整窗口大小,并且图像适应窗口
.git ignore
忽略文件夹:文件夹名/
特定路径下文件夹:文件夹名/文件夹名/
忽略文件:文件名.后缀
特定路径下文件:文件夹名/文件名.后缀
忽略所有特定类型的文件: *.后缀
push GitHub
在GitHub建立repositor远程仓库,添加LICENSE和README
在本地项目git init初始化本地仓库
git remote add origin https://github.com/…….git与远程仓库关联
git pull origin master 把远程仓库和本地同步,消除差异
git add *把本地项目推送到本地暂存区
git commit -m"……"把暂存区推送到本地仓库
git push origin master把本地仓库推送到远程仓库
Vertex buffers
我们将用memory内存中的顶点缓冲区替换顶点着色器中的顶点数据部分
创建一个CPU可见缓冲区,并使用memcpy将顶点数据直接复制到其中,然后我们将看到如何使用暂存缓冲区将顶点数据复制到高性能内存
修改vertex data
首先shader中不再包含data,修改为layout(location = x)的形式,以便稍后可以使用这些索引来引用它们,它们是在Vertex buffers中按顶点指定的属性,
然后将之前vertex data移动到程序中,首先创建struct表示每个顶点的数据类型,并创建顶点数据数组
struct Vertex {glm::vec2 pos;glm::vec3 color;
}
Binding descriptions
当数据传到GPU内存后,如何传递给顶点着色器,传递此信息需要两种类型的结构
第一个结构是VkVertexInputBindingDescription顶点输入绑定描述,描述了在整个顶点中从内存加载数据的速率
stride指定数据条目之间的字节数
inputRate参数可以具有以下值之一
- VK_VERTEX_INPUT_RATE_VERTEX:移动到每个顶点后的下一个数据条目
- VK_VERTEX_INPUT_RATE_RELANCE:在每个实例之后移动到下一个数据条目
我们将向vertex结构体添加一个成员函数getBindingDescription
static VkVertexInputBindingDescription getBindingDescription() {VkVertexInputBindingDescription bindingDescription{};bindingDescription.binding = 0;bindingDescription.stride = sizeof(Vertex);bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;return bindingDescription;
}
Attribute descriptions
VkVertexInputAttributeDescription顶点输入属性描述,描述如何从源自绑定描述的顶点数据块中提取顶点属性
binding参数告诉Vulkan逐顶点数据来自哪个绑定
location参数引用顶点着色器中输入的location指令
format参数描述属性的数据类型字节大小,格式是使用与颜色格式相同的枚举指定的。以下着色器类型和格式通常一起使用
- (R)float: VK_FORMAT_R32_SFLOAT
- (RG)• vec2: VK_FORMAT_R32G32_SFLOAT
- (RGB)• vec3: VK_FORMAT_R32G32B32_SFLOAT
- (RGBA)• vec4: VK_FORMAT_R32G32B32A32_SFLOAT
offset参数指定从要读取的逐顶点数据开始后的字节数
使用offsetof宏可以自动计算
我们将向vertex结构体添加另一个成员函数getAttributeDescriptions,这里使用的是 std::array,应引入头文件
static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};attributeDescriptions[0].binding = 0;attributeDescriptions[0].location = 0;attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;attributeDescriptions[0].offset = offsetof(Vertex, pos);attributeDescriptions[1].binding = 0;attributeDescriptions[1].location = 1;attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;attributeDescriptions[1].offset = offsetof(Vertex, color);return attributeDescriptions;
}
还要在createGraphicsPipeline中更改VkPipelineVertexInputStateCreateInfo部分的vertexInputInfo
Vertex buffer creation
缓冲区是用于存储图形显卡(GPU)可以读取的任意数据(比如vertex data)的内存区域
向往常一样VkBufferCreateInfo,vkCreateBuffer,vkDestroyBuffer
size指定缓冲区的大小(以字节为单位),使用sizeof可以直接计算顶点数据的字节大小。
usage指示缓冲区中的数据将用于哪些目的,比如作为顶点缓冲区
就像交换链中的图像一样,缓冲区也可以由特定的队列家族拥有,或者同时在多个队列家族之间共享,但是我们仅想在图形队列中使用,因此我们可以坚持独占访问
flags参数用于配置稀疏缓冲区内存
Search Memory requirements
vexture buffer已经创建,但是还尚未分配任何内存, 它不会自动为自己分配内存,
首先应通过vkGetBufferMemoryRequirements函数查询其内存需求
VkMemoryRequirements结构体有三个字段
- size:所需内存的大小(以字节为单位)
- alignment:缓冲区在已分配内存区域中开始的偏移量(以字节为单位)
- memoryTypeBits:适合缓冲区的内存类型的位字段。
从显卡找到需要的内存索引:
显卡可以提供不同类型的内存来分配。每种类型的内存在允许的操作和性能特征方面有所不同。我们需要查找和memoryTypeBits匹配的内存类型索引
首先通过vkGetPhysicalDeviceMemoryProperties查询有关可用内存类型的信息,可以获得VkPhysicalDeviceMemoryProperties物理设备内存属性,
它有两个数组memoryTypes和memoryHeaps我们只关心内存的类型,不关心内存堆
我们可以通过简单地迭代memoryTypeBits并检查相应的位是否设置为1来找到合适的内存类型的索引
然而,我们不仅仅对适合顶点缓冲区的内存类型感兴趣。我们还需要能够将顶点数据写入该内存
循环来检查memoryTypes是否支持这两个属性
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT支持从CPU写入数据到内存 | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT确保映射的内存总是与分配的内存的内容相匹配,
Memory allocation
我们可以通过填充VkMemoryAllocateInfo结构来在GPU分配内存
通过vkBindBufferMemory将此内存与缓冲区相关联,它的第四个参数是内存区域内的偏移量
还应该vkFreeMemory清除内存
Filling the vertex buffer
现在将顶点数据复制到缓冲区
vkMapMemory用于将设备内存(GPU)映射到主机(CPU)地址空间,这样,CPU 就可以访问设备内存了
- 参数2想要映射到 CPU 地址空间的内存块
- 第3个参数是偏移量第4个参数是大小,
- 倒数第二个参数可用于指定标志,
- 最后一个参数指定指向映射内存的指针的输出,
memcpy将数据复制到data的内存即等同于复制到vertex buffer内存中
vkUnmapMemory映射内存通常是临时的,使用完毕后需要调用 解除映射
但是在vkUnmapMemory时,驱动程序可能不会立即将数据复制到缓冲存储器中,有两种方法可以解决这个问题:
它确保映射的内存总是与分配的内存的内容相匹配,这导致了比显式刷新稍差的性能
- 一种是之前检查的VK_MEMORY_PROPERTY_HOST_COHERENT_BIT内存类型
- 在写入映射存储器之后调用vkFlushMappedMemoryRanges且在从映射存储器阅读之前调用vkInvalidateMappedMemoryRanges
Binding the vertex buffer
应该在recordCommandBuffer录制命令函数中,应该调用vkCmdBindVertexBuffers,绑定顶点缓冲区
前两个参数指定了我们将要指定顶点缓冲区的绑定的偏移量和数量
最后两个参数指定要绑定的顶点缓冲区数组和开始阅读顶点数据的字节偏移量
现在运行程序,确保没有错误