본문 바로가기

Vulkan

Vertex buffer creation

이전 글 : Vertex input description

다음 글 : Staging buffer



Vertex buffer creation
버텍스 버퍼 생성


Introduction


Vulkan의 버퍼는 그래픽 카드에서 읽을 수 있는 임의의 데이터를 저장하는 데 사용되는 메모리 영역입니다. 이것들은 정점 데이터를 저장하는 데 사용할 수 있습니다.이 장에서는 앞으로 다룰 다른 많은 목적으로 사용할 수 있습니다. 지금까지 다루어 왔던 Vulkan 객체와 달리 버퍼는 자동으로 메모리를 할당하지 않습니다. 이전 장들의 설명에서  Vulkan API는 프로그래머가 거의 모든 것을 제어 할 수 있다는 것을 보여 주었고 메모리 관리는 이러한 것들 중 하나입니다.



Buffer creation


새 함수를 createVertexBuffer만들고 initVulkan바로 전에 호출하십시오 createCommandBuffers.


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

...

void createVertexBuffer() {

}


버퍼를 만들기 위해 VkBufferCreateInfo 구조체 정보를 준비해야 합니다.


VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = sizeof(vertices[0]) * vertices.size();


구조체의 첫 번째 필드는 size바이트 단위로 버퍼 크기를 지정합니다. 정점 데이터의 바이트 크기를 계산하는 것은 간단합니다 sizeof.


bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;


두 번째 필드는 usage, 버퍼의 데이터가 어떤 용도로 사용되는지 나타냅니다.

bitwise 등을  사용하여 원하는 여러 목적을 지정할 수 있습니다.지금의 버퍼 생성 목적은 정점 버퍼가 될 것이고, 앞으로 다른 장에서 다른 유형의 사용법을 살펴볼 것이다.

bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;


스왑 체인의 이미지와 마찬가지로 버퍼는 특정 대기열 패밀리가 소유하거나 여러 시간에 동시에 공유 될 수 있습니다. 버퍼는 그래픽 대기열에서만 사용되므로 독점적인 액세스를 유지할 수 있습니다.

flags 매개 변수는 현재 관련이 없는 희소한 버퍼 메모리를 구성하는 데 사용됩니다. 우리는 기본값 0으로 남겨 둘 것입니다. 이제 vkCreateBuffer로 버퍼를 생성 할 수 있습니다. 버퍼 핸들을 보유 할 클래스 멤버를 정의하고 vertexBuffer를 호출합니다.


VkBuffer vertexBuffer;

...

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


버퍼는 프로그램 종료까지 명령 렌더링에 사용할 수 있어야 하며 스왑 체인에 의존하지 않으므로 cleanup 함수에서 호출하여 제거 합니다.


void cleanup() {
   cleanupSwapChain();

   vkDestroyBuffer(device, vertexBuffer, nullptr);

   ...
}




Memory requirements


버퍼가 생성 되었지만 아직 실제로 할당 된 메모리가 없습니다. 버퍼에 메모리를 할당하는 첫 번째 단계는 적절한 vkGetBufferMemoryRequirements 함수를 사용하여 메모리 요구 사항을 쿼리하는 것입니다.


VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);


VkMemoryRequirements 구조체에는 세 개의 필드가 있습니다.


  • size : 필요한 메모리 양 (바이트)의 크기, bufferInfo.size와 다를 수 있습니다.

  • alignment : 할당 된 메모리 영역에서 버퍼가 시작되는 바이트 오프셋. bufferInfo.usage와 bufferInfo.flags에 의존한다.

  • memoryTypeBits : 버퍼에 적합한 메모리 유형의 비트 필드.


그래픽 카드는 할당 할 수있는 여러 유형의 메모리를 제공 할 수 있습니다. 각 유형의 메모리는 허용되는 작업 및 성능 특성에 따라 다릅니다. 버퍼의 요구 사항과 자체 애플리케이션 요구 사항을 결합하여 사용할 올바른 유형의 메모리를 찾아야합니다. 이 목적을 위해 findMemoryType 이라는 새로운 함수를 만들어 보겠습니다.


uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {

}


먼저 vkGetPhysicalDeviceMemoryProperties 를 사용하여 사용 가능한 메모리 유형에 대한 정보를 쿼리해야합니다.


VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);


VkPhysicalDeviceMemoryProperties 구조체에는 두 개의 배열 memoryTypes 및 memoryHeaps 가 있습니다. 메모리 힙은 VRAM이 부족한 경우 RAM의 전용 VRAM 및 스왑 공간과 같은 별개의 메모리 리소스입니다. 이 힙에는 여러 유형의 메모리가 있습니다. 지금 우리는 메모리의 유형에 대해서만 관심을 가질 것이고 그것이 위치한 힙이 아니라 성능에 영향을 미칠 수 있다고 상상할 수 있습니다.


먼저 버퍼 자체에 적합한 메모리 유형을 찾으십시오.


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

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


typeFilter 매개 변수는 적절한 메모리 유형의 비트 필드를 지정하는 데 사용됩니다. 즉, 단순히 반복하여 해당 비트가 1로 설정되어 있는지 확인하여 적절한 메모리 유형의 인덱스를 찾을 수 있습니다.


그러나 정점 버퍼에 적합한 메모리 유형에만 관심이있는 것은 아닙니다. 또한 정점 데이터를 해당 메모리에 쓸 수 있어야합니다. memoryTypes 배열은 각 메모리 유형의 힙 및 속성을 지정하는 VkMemoryType 구조체로 구성됩니다. 속성은 메모리의 특수 기능을 정의합니다. 예를 들어 CPU에서 쓸 수 있도록 매핑 할 수 있습니다. 이 속성은 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT로 표시되지만 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 속성도 사용해야 합니다.


이제 이 속성의 지원을 확인하기 위해 루프를 수정할 수 있습니다.


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


둘 이상의 속성을 가질 수 있으므로 비트 AND의 결과가 단지 0이 아닌 원하는 속성 비트 필드와 같은지 확인해야 합니다. 필요로 하는 모든 프로퍼티를 가지는 버퍼에 적절한 메모리 형이 존재하면, 그 인덱스를 돌려 주어, 그렇지 않은 경우는 예외를 오류를 발생 합니다.



Memory allocation


우리는 올바른 메모리 타입을 결정하는 방법을 알아 보았습니다. 그래서 VkMemoryAllocateInfo 구조체를 채워 메모리를 실제로 할당 할 수 있습니다.


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);


메모리 할당은 이제 크기와 유형을 지정하는 것처럼 간단합니다. 둘 다 정점 버퍼의 메모리 요구 사항과 원하는 속성에서 파생됩니다. 핸들을 메모리에 저장하고 vkAllocateMemory로 할당 할 클래스 멤버를 만듭니다.


VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;

...

if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
   throw std::runtime_error("failed to allocate vertex buffer memory!");
}


메모리 할당이 성공하면 vkBindBufferMemory 를 사용하여이 메모리를 버퍼와 연결할 수 있습니다.


vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);


처음 세 매개 변수는 무엇인지 바로 알수 있습니다. 네 번째 매개 변수는 메모리 영역 내의 오프셋입니다. 이 메모리는 정점 버퍼에 대해 특별히 할당되기 때문에 오프셋은 단순히 0입니다. 오프셋이 0이 아닌 경우 memRequirements.alignment로 나눌 수 있어야 합니다.


물론 C ++의 동적 메모리 할당과 마찬가지로 메모리는 어느 시점에서 해제 되어야 합니다. 버퍼 객체에 바인드 된 메모리는 버퍼가 더 이상 사용되지 않을 경우 해제 해야 하므로 버퍼가 삭제 된 후 해제 하십시오.


void cleanup() {
   cleanupSwapChain();

   vkDestroyBuffer(device, vertexBuffer, nullptr);
   vkFreeMemory(device, vertexBufferMemory, nullptr);



Filling the vertex buffer


이제 버텍스 데이터를 버퍼에 복사 할 차례입니다. 이는 vkMapMemory 를 사용하여 버퍼 메모리를 CPU 가 액세스 가능 하도록 메모리에 매핑하여 수행됩니다.


void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);


이 함수는 오프셋과 크기로 정의 된 지정된 메모리 리소스의 영역에 액세스 할 수 있게합니다. 여기서 오프셋과 크기는 각각 0과 bufferInfo.size 입니다. 특수 값 VK_WHOLE_SIZE를 지정하여 모든 메모리를 맵핑 할 수도 있습니다. 두 번째 매개 변수는 플래그를 지정하는 데 사용할 수 있지만 현재 API에는 아직 사용할 수 있는 매개 변수가 없습니다. 마지막 값은 매핑 된 메모리에 대한 포인터의 출력을 지정합니다.

void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
   memcpy(data, vertices.data(), (size_t) bufferInfo.size);
vkUnmapMemory(device, vertexBufferMemory);



이제 vkUnmapMemory를 사용하여 버텍스 데이터를 매핑 된 메모리에 memcpy하고 다시 매핑 해제 할 수 있습니다. 불행히도 드라이버는 예를 들어 캐싱 때문에 버퍼 메모리에 데이터를 즉시 복사하지 않을 수 있습니다. 버퍼에 쓰기가 매핑 된 메모리에 아직 표시되지 않을 수도 있습니다. 이 문제를 해결할 수있는 두 가지 방법이 있습니다.


  • VK_MEMORY_PROPERTY_HOST_COHERENT_BIT와 함께 표시되는 호스트 일관된 메모리 힙을 사용하십시오.

  • 매핑 된 메모리에 쓰기 후에 vkFlushMappedMemoryRanges를 호출하고 매핑 된 메모리에서 읽기 전에 vkInvalidateMappedMemoryRanges를 호출하십시오.


우리는 매핑 된 메모리가 할당 된 메모리의 내용과 항상 일치하는지 확인하는 첫 번째 방법을 찾아갔습니다. 명시적 플러싱보다 성능이 약간 떨어질 수 있지만 다음 장에서 그 값이 중요한 이유를 알 수 있습니다.





Binding the vertex buffer


현재 남아있는 것은 렌더링 작업 중 정점 버퍼를 바인딩하는 것뿐입니다. 이를 위해 createCommandBuffers 함수를 확장 할 것입니다.


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);


vkCmdBindVertexBuffers 함수는 이전 장에서 설정 한 것과 같이 바인딩에 정점 버퍼를 바인딩하는 데 사용됩니다. 커멘드 · 버퍼 외의 최초의 2 개의 파라미터는, 정점 버퍼를 지정하는 오프셋 및 바이너리의 수를 지정합니다. 마지막 두 매개 변수는 바인딩 할 정점 버퍼의 배열을 지정하고 정점 데이터를 읽는 바이트 오프셋을 지정합니다. 또한 vkCmdDraw에 대한 호출을 변경하여 하드 코드 된 숫자 3과 반대로 버퍼의 정점 수를 전달해야 합니다.



이제 프로그램을 실행하면 익숙한 삼각형이 다시 보일 것입니다.



정점 배열을 수정하여 상단 정점의 색상을 흰색으로 변경해보십시오.


const std::vector<Vertex> vertices = {
   {{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}},
   {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
   {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};



다음 장에서는 버텍스 데이터를 버텍스 버퍼에 복사하는 다른 방법을 살펴 보겠습니다. 이는 성능이 향상 되지만 더 많은 작업이 필요합니다.


C++ code / Vertex shader / Fragment shader


이전 글 : Vertex input description

다음 글 : Staging buffer




'Vulkan' 카테고리의 다른 글

Index buffer  (0) 2018.01.25
Staging buffer  (0) 2018.01.25
Vertex input description  (0) 2018.01.25
Swap chain recreation  (0) 2018.01.25
Drawing - Rendering and presentation  (0) 2018.01.25