天天看點

初識Vulkun(16):頂點緩沖區前言修改頂點着色器準備頂點資料頂點輸入頂點緩沖區指令緩沖區記錄頂點緩沖區知識補充總結

08/09/2020

文章目錄

  • 前言
  • 修改頂點着色器
  • 準備頂點資料
  • 頂點輸入
    • 綁定描述
    • 屬性描述
    • 更改管線頂點輸入描述
  • 頂點緩沖區
    • 建立緩沖區
    • 為緩沖區配置設定記憶體
      • 找比對的記憶體類型的索引
    • 傳資料到頂點緩沖區
  • 指令緩沖區記錄頂點緩沖區
  • 知識補充
  • 總結

前言

我們将會使用記憶體頂點緩沖區來替換之前寫死到頂點着色器中的頂點資料。我們将從最簡單的方法開始建立一個CPU可見的緩沖區,并使用memcpy将頂點資料直接複制到緩沖區,之後将會使用暫存緩沖區将頂點資料指派到高性能的顯存中。

着色器使用頂點輸入有兩個方法,一個由CPU提供記憶體的頂點緩沖區,還有一個由GPU提供顯存的頂點緩沖區,需要用到臨時緩沖區。

修改頂點着色器

#version 450
#extension GL_ARB_separate_shader_objects:enable

layout(location = 0) out vec3 fragColor;

layout(location = 0) in vec2 inPosition;			
layout(location = 1) in vec3 inColor;
void main(){
	gl_Position = vec4(inPosition,0.0,1.0);
	fragColor = inColor;
}
           
  • in 關鍵字,頂點着色器讀取頂點緩沖區的頂點輸入/資料
  • 描述布局,位置和類型,名字無所謂

準備頂點資料

GLM提供向量和矩陣之類的線性代數資料結構

#include <glm/glm.hpp>
struct Vertex 
{
	glm::vec2 pos;
	glm::vec3 color;
}

const std::vector<Vertex> vertices = 
{
	{{0.0f,-0.5f},{1.0f,0.0f,0.0f}},
	{{0.5f,0.5f},{0.0f,1.0f,0.0f}},
	{{-0.5f,0.5f},{0.0f,0.0f,1.0f}}
};
           
  • 确定類型,與頂點着色器中的聲明保持一緻

頂點輸入

一旦資料類型被送出到GPU的顯存中,就需要告訴Vulkan傳遞到頂點着色器中的資料格式。

  • 确定位置(location = 0)
  • 确定類型(vec2)
struct Vertex 
{
	glm::vec2 pos;
	glm::vec3 color;

	static VkVertexInputBindingDescription getBindingDescription()
	{
		VkVertexInputBindingDescription bindingDescription{};
		bindingDescription.binding = 0;								//數組中的索引,現在隻有一組數組
		bindingDescription.stride = sizeof(Vertex);
		bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;	//頂點資料
		return bindingDescription;
	}

	static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescription() 
	{
		std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
		attributeDescriptions[0].binding = 0;						//與上面的binding相呼應 
		attributeDescriptions[0].location = 0;						//頂點坐标 -- 确定在頂點着色器中的位置
		attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;	//類型識vec2
		attributeDescriptions[0].offset = offsetof(Vertex, pos);	//在記憶體中偏移量,由offsetof計算出來

		attributeDescriptions[1].binding = 0;
		attributeDescriptions[1].location = 1;						//顔色位置
		attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
		attributeDescriptions[1].offset = offsetof(Vertex, color);

		return attributeDescriptions;
	}
};
           

綁定描述

在整個頂點資料從記憶體加載的速率。即它指定資料條目之間間隔的位元組數以及是否每個頂點之後或者每個instance之後移動到下一個條目。即頂點間隔。

  • binding 代表數組中的索引
  • stride 每個數組元素之間的間隔,以位元組數表示
  • inputRate:移動到每個頂點後的下一個條目或者每個instance後移動到下一個條目
  • 這裡使用Vertex表示per-vertex data

屬性描述

如何描述結構體中每個元素的屬性。pos類型是vec2,是第一個屬性,在頂點着色器的位置是0。

  • binding: 每個頂點資料的來源
  • location:頂點着色器中的位置
  • format:類型是vec2

更改管線頂點輸入描述

管道需要确定頂點資料的類型,即上面定義好的屬性和頂點描述

auto bindingDescription = Vertex::getBindingDescription();
auto attributeDescription = Vertex::getAttributeDescription();


//the format of the vertex data that will be passed to the vertex shader
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; //Optional
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescription.size());
vertexInputInfo.pVertexAttributeDescriptions = attributeDescription.data();
           

頂點緩沖區

Vulkan 建立頂點緩沖區,在Vulkan中,緩沖區是記憶體的一塊區域,該區域用于向顯示卡提供預要讀取的任意資料。它們可以用來存儲頂點資料,也可以用于其他目的。與之前建立的Vulkan對象不同的是,緩沖區自己不會配置設定記憶體空間。

建立緩沖區

  • 存儲資料的大小
  • 使用類型
  • 分享模式
void createVertexBuffer() 
{
	VkBufferCreateInfo bufferInfo{};
	bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
	bufferInfo.size = sizeof(vertices[0]) * vertices.size();
	bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
	bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

	if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) 
	{
		throw std::runtime_error("failed to create vertex buffer");
	}
}
           

為緩沖區配置設定記憶體

首先實體裝置GPU查詢支援的記憶體屬性,找到适合頂點緩沖區的記憶體要求的索引。

void createVertexBuffer() 
{
	//Create part...
	
	//allocate memory
	VkMemoryRequirements memRequirements;
	vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);
	
	VkMemoryAllocateInfo allocInfo{};
	allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
	allocInfo.allocationSize = memRequirements.size;
	allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);			//記憶體類型的索引
	
	if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
		throw std::runtime_error("failed to allocate vertex buffer memory!");
	}
	
	//連接配接緩沖區和記憶體
	vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);

}
           

VkMemoryRequirements結構體有三個字段:

  • size: 需要的記憶體位元組大小,可能與bufferInfo.size大小不一緻。
  • alignment: 緩沖區的記憶體配置設定區域開始的位元組偏移量,它取決于bufferInfo.usage和bufferInfo.flags。
  • memoryTypeBits: 适用于緩沖區的存儲器類型的位字段。

找比對的記憶體類型的索引

顯示卡可以配置設定不同類型的記憶體。每種類型的記憶體根據所允許的操作和特性均不相同。我們需要結合緩沖區與應用程式實際的需要找到正确的記憶體類型使用。現在添加一個新的函數完成此邏輯findMemoryType。

uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
		VkPhysicalDeviceMemoryProperties memProperties;
		vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);

		for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
			if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
				return i;
			}
		}

		throw std::runtime_error("failed to find suitable memory type!");
	}
           

傳資料到頂點緩沖區

void createVertexBuffer() 
{
	//Create part...
	
	//allocate memeory
	
	//transfer data
	void* data;
	vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
	memcpy(data, vertices.data(), (size_t)bufferInfo.size);
	vkUnmapMemory(device, vertexBufferMemory);
}
           
  • 使用vkMapMemory将緩沖區記憶體映射(mapping the buffer memory)到CPU可通路的記憶體中完成。
  • memcpy将頂點資料拷貝到映射記憶體中,并使用vkUnmapMemory取消映射。

指令緩沖區記錄頂點緩沖區

類似與聲明了一塊頂點緩沖區并且開辟了一塊記憶體大小

void createVertexBuffers
{

	//前面幾步
	
	//記錄指令
	vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
	
	VkBuffer vertexBuffers[] = {vertexBuffer};
	VkDeviceSize offsets[] = {0};
	vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);
	
	vkCmdDraw(commandBuffers[i], static_cast<uint32_t>(vertices.size()), 1, 0, 0);
}

           

知識補充

  • 頂點輸入的格式:SFLOAT,SINT,SUINT,SFLOAT
  • 記憶體特性:比如從CPU寫入資料
    • HOST_VISIBLE
    • HOST_COHERENT
  • HOST_COHERENT_BIT: 由于緩存,驅動程式有可能不會立刻拷貝資料到緩沖區的記憶體中去

總結

  • 設定頂點輸入布局(VkPipelineVertexInputStateCreateInfo)
  • 建立頂點緩沖區(VkBuffer)和配置設定顯存(VkDeviceBuffer)
  • 傳輸資料(vkMapMemory,memcpy,vkUnmapMemory)
  • 在指令緩沖區,綁定頂點緩沖區(vkCmdBindVertexBuffers),可以綁定多個頂點緩沖區