이전 글 : Vertex input description
Vertex buffer creation
버텍스 버퍼 생성
Introduction
Vulkan의 버퍼는 그래픽 카드에서 읽을 수 있는 임의의 데이터를 저장하는 데 사용되는 메모리 영역입니다. 이것들은 정점 데이터를 저장하는 데 사용할 수 있습니다.이 장에서는 앞으로 다룰 다른 많은 목적으로 사용할 수 있습니다. 지금까지 다루어 왔던 Vulkan 객체와 달리 버퍼는 자동으로 메모리를 할당하지 않습니다. 이전 장들의 설명에서 Vulkan API는 프로그래머가 거의 모든 것을 제어 할 수 있다는 것을 보여 주었고 메모리 관리는 이러한 것들 중 하나입니다.
Buffer creation
새 함수를 createVertexBuffer만들고 initVulkan바로 전에 호출하십시오 createCommandBuffers.
void initVulkan() { |
버퍼를 만들기 위해 VkBufferCreateInfo 구조체 정보를 준비해야 합니다.
VkBufferCreateInfo bufferInfo = {}; |
구조체의 첫 번째 필드는 size바이트 단위로 버퍼 크기를 지정합니다. 정점 데이터의 바이트 크기를 계산하는 것은 간단합니다 sizeof.
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; |
두 번째 필드는 usage, 버퍼의 데이터가 어떤 용도로 사용되는지 나타냅니다.
bitwise 등을 사용하여 원하는 여러 목적을 지정할 수 있습니다.지금의 버퍼 생성 목적은 정점 버퍼가 될 것이고, 앞으로 다른 장에서 다른 유형의 사용법을 살펴볼 것이다.
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; |
스왑 체인의 이미지와 마찬가지로 버퍼는 특정 대기열 패밀리가 소유하거나 여러 시간에 동시에 공유 될 수 있습니다. 버퍼는 그래픽 대기열에서만 사용되므로 독점적인 액세스를 유지할 수 있습니다.
flags 매개 변수는 현재 관련이 없는 희소한 버퍼 메모리를 구성하는 데 사용됩니다. 우리는 기본값 0으로 남겨 둘 것입니다. 이제 vkCreateBuffer로 버퍼를 생성 할 수 있습니다. 버퍼 핸들을 보유 할 클래스 멤버를 정의하고 vertexBuffer를 호출합니다.
VkBuffer vertexBuffer; |
버퍼는 프로그램 종료까지 명령 렌더링에 사용할 수 있어야 하며 스왑 체인에 의존하지 않으므로 cleanup 함수에서 호출하여 제거 합니다.
void cleanup() { |
Memory requirements
버퍼가 생성 되었지만 아직 실제로 할당 된 메모리가 없습니다. 버퍼에 메모리를 할당하는 첫 번째 단계는 적절한 vkGetBufferMemoryRequirements 함수를 사용하여 메모리 요구 사항을 쿼리하는 것입니다.
VkMemoryRequirements memRequirements; |
VkMemoryRequirements 구조체에는 세 개의 필드가 있습니다.
size : 필요한 메모리 양 (바이트)의 크기, bufferInfo.size와 다를 수 있습니다.
alignment : 할당 된 메모리 영역에서 버퍼가 시작되는 바이트 오프셋. bufferInfo.usage와 bufferInfo.flags에 의존한다.
memoryTypeBits : 버퍼에 적합한 메모리 유형의 비트 필드.
그래픽 카드는 할당 할 수있는 여러 유형의 메모리를 제공 할 수 있습니다. 각 유형의 메모리는 허용되는 작업 및 성능 특성에 따라 다릅니다. 버퍼의 요구 사항과 자체 애플리케이션 요구 사항을 결합하여 사용할 올바른 유형의 메모리를 찾아야합니다. 이 목적을 위해 findMemoryType 이라는 새로운 함수를 만들어 보겠습니다.
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { |
먼저 vkGetPhysicalDeviceMemoryProperties 를 사용하여 사용 가능한 메모리 유형에 대한 정보를 쿼리해야합니다.
VkPhysicalDeviceMemoryProperties memProperties; |
VkPhysicalDeviceMemoryProperties 구조체에는 두 개의 배열 memoryTypes 및 memoryHeaps 가 있습니다. 메모리 힙은 VRAM이 부족한 경우 RAM의 전용 VRAM 및 스왑 공간과 같은 별개의 메모리 리소스입니다. 이 힙에는 여러 유형의 메모리가 있습니다. 지금 우리는 메모리의 유형에 대해서만 관심을 가질 것이고 그것이 위치한 힙이 아니라 성능에 영향을 미칠 수 있다고 상상할 수 있습니다.
먼저 버퍼 자체에 적합한 메모리 유형을 찾으십시오.
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { |
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++) { |
둘 이상의 속성을 가질 수 있으므로 비트 AND의 결과가 단지 0이 아닌 원하는 속성 비트 필드와 같은지 확인해야 합니다. 필요로 하는 모든 프로퍼티를 가지는 버퍼에 적절한 메모리 형이 존재하면, 그 인덱스를 돌려 주어, 그렇지 않은 경우는 예외를 오류를 발생 합니다.
Memory allocation
우리는 올바른 메모리 타입을 결정하는 방법을 알아 보았습니다. 그래서 VkMemoryAllocateInfo 구조체를 채워 메모리를 실제로 할당 할 수 있습니다.
VkMemoryAllocateInfo allocInfo = {}; |
메모리 할당은 이제 크기와 유형을 지정하는 것처럼 간단합니다. 둘 다 정점 버퍼의 메모리 요구 사항과 원하는 속성에서 파생됩니다. 핸들을 메모리에 저장하고 vkAllocateMemory로 할당 할 클래스 멤버를 만듭니다.
VkBuffer vertexBuffer; |
메모리 할당이 성공하면 vkBindBufferMemory 를 사용하여이 메모리를 버퍼와 연결할 수 있습니다.
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); |
처음 세 매개 변수는 무엇인지 바로 알수 있습니다. 네 번째 매개 변수는 메모리 영역 내의 오프셋입니다. 이 메모리는 정점 버퍼에 대해 특별히 할당되기 때문에 오프셋은 단순히 0입니다. 오프셋이 0이 아닌 경우 memRequirements.alignment로 나눌 수 있어야 합니다.
물론 C ++의 동적 메모리 할당과 마찬가지로 메모리는 어느 시점에서 해제 되어야 합니다. 버퍼 객체에 바인드 된 메모리는 버퍼가 더 이상 사용되지 않을 경우 해제 해야 하므로 버퍼가 삭제 된 후 해제 하십시오.
void cleanup() { |
Filling the vertex buffer
이제 버텍스 데이터를 버퍼에 복사 할 차례입니다. 이는 vkMapMemory 를 사용하여 버퍼 메모리를 CPU 가 액세스 가능 하도록 메모리에 매핑하여 수행됩니다.
void* data; |
이 함수는 오프셋과 크기로 정의 된 지정된 메모리 리소스의 영역에 액세스 할 수 있게합니다. 여기서 오프셋과 크기는 각각 0과 bufferInfo.size 입니다. 특수 값 VK_WHOLE_SIZE를 지정하여 모든 메모리를 맵핑 할 수도 있습니다. 두 번째 매개 변수는 플래그를 지정하는 데 사용할 수 있지만 현재 API에는 아직 사용할 수 있는 매개 변수가 없습니다. 마지막 값은 매핑 된 메모리에 대한 포인터의 출력을 지정합니다.
void* data; |
이제 vkUnmapMemory를 사용하여 버텍스 데이터를 매핑 된 메모리에 memcpy하고 다시 매핑 해제 할 수 있습니다. 불행히도 드라이버는 예를 들어 캐싱 때문에 버퍼 메모리에 데이터를 즉시 복사하지 않을 수 있습니다. 버퍼에 쓰기가 매핑 된 메모리에 아직 표시되지 않을 수도 있습니다. 이 문제를 해결할 수있는 두 가지 방법이 있습니다.
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT와 함께 표시되는 호스트 일관된 메모리 힙을 사용하십시오.
매핑 된 메모리에 쓰기 후에 vkFlushMappedMemoryRanges를 호출하고 매핑 된 메모리에서 읽기 전에 vkInvalidateMappedMemoryRanges를 호출하십시오.
우리는 매핑 된 메모리가 할당 된 메모리의 내용과 항상 일치하는지 확인하는 첫 번째 방법을 찾아갔습니다. 명시적 플러싱보다 성능이 약간 떨어질 수 있지만 다음 장에서 그 값이 중요한 이유를 알 수 있습니다.
Binding the vertex buffer
현재 남아있는 것은 렌더링 작업 중 정점 버퍼를 바인딩하는 것뿐입니다. 이를 위해 createCommandBuffers 함수를 확장 할 것입니다.
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); |
vkCmdBindVertexBuffers 함수는 이전 장에서 설정 한 것과 같이 바인딩에 정점 버퍼를 바인딩하는 데 사용됩니다. 커멘드 · 버퍼 외의 최초의 2 개의 파라미터는, 정점 버퍼를 지정하는 오프셋 및 바이너리의 수를 지정합니다. 마지막 두 매개 변수는 바인딩 할 정점 버퍼의 배열을 지정하고 정점 데이터를 읽는 바이트 오프셋을 지정합니다. 또한 vkCmdDraw에 대한 호출을 변경하여 하드 코드 된 숫자 3과 반대로 버퍼의 정점 수를 전달해야 합니다.
이제 프로그램을 실행하면 익숙한 삼각형이 다시 보일 것입니다.
정점 배열을 수정하여 상단 정점의 색상을 흰색으로 변경해보십시오.
const std::vector<Vertex> vertices = { |
다음 장에서는 버텍스 데이터를 버텍스 버퍼에 복사하는 다른 방법을 살펴 보겠습니다. 이는 성능이 향상 되지만 더 많은 작업이 필요합니다.
C++ code / Vertex shader / Fragment shader
이전 글 : Vertex input description
'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 |