Vulkan 튜토리얼: 디스크립터 셋 할당 및 커맨드 실행하기
이번 튜토리얼에서 소주제였던 버퍼 입출력을 완료할 것입니다. 이를 수행하기 위해, 다음의 네 과정을 순차적으로 수행해야 합니다:
- 컴퓨트 파이프라인을 완성했기 때문에, 이젠 이 파이프라인을 실행하기만 하면 됩니다. 다만 앞서 설명했듯 파이프라인은 사용할 디스크립터 셋에 대한 정보만을 담고 있을 뿐, 아직은 우리가 만든 버퍼를 파이프라인에 바인딩하지는 않았습니다. 이를 위해서는 실제 버퍼를 가르킬 디스크립터 셋을 할당할 디스크립터 풀 (descriptor pool)을 생성해야 합니다.
- 생성한 디스크립터 풀로부터 디스크립터 셋을 할당하고, 디바이스를 이용해 디스크립터 셋에 버퍼 정보를 기록하는 디스크립터 업데이트 과정을 거쳐야 합니다.
- 모든게 끝났다면 커맨드 풀 (command pool)로부터 커맨드 버퍼 (command buffer)를 할당하고, 파이프라인과 디스크립터 셋을 커맨드 버퍼에 바인딩 한 후, 파이프라인을 실행하는 디스패치 커맨드를 커맨드 버퍼에 기록해야 합니다.
- 커맨드 버퍼 기록이 끝났다면 커맨드 버퍼를 큐에 제출하고, 큐가 작업을 수행을 완료할 때까지 기다립니다.
한 가지 더, 이번에 다룰 디스크립터 풀-디스크립터 셋, 커맨드 풀-커맨드 버퍼의 관계는 Vulkan-Hpp의 RAII 패턴에서 조금 특이한 소유권을 가지고 있습니다. 이에 대해서도 알아볼 것입니다.
디스크립터 풀 생성 및 디스크립터 셋 할당하기
디스크립터 풀 생성하기
디스크립터 셋을 만들기 위해서는 그 풀(pool)이 필요하며, 그 역할을 하는 것이 디스크립터 풀입니다. 디스크립터 풀은 해당 풀이 생성할 총 디스크립터의 정보와 디스크립터 셋의 개수를 풀 생성 시점에 명시해야 합니다. 생성할 총 디스크립터의 정보는 연속된 vk::DescriptorPoolSize
로 명시될 수 있습니다.
Subject: [PATCH] Create descriptor pool.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -184,5 +184,18 @@
*pipelineLayout,
} };
+ // Create descriptor pool.
+ const vk::raii::DescriptorPool descriptorPool = [&] {
+ constexpr vk::DescriptorPoolSize poolSize {
+ vk::DescriptorType::eStorageBuffer,
+ 1,
+ };
+ return vk::raii::DescriptorPool { device, vk::DescriptorPoolCreateInfo {
+ {},
+ 1,
+ poolSize,
+ } };
+ }();
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
디스크립터 풀 생성 정보 구조체의 두 번째 인수는 디스크립터 풀로부터 할당할 총 디스크립터 셋의 개수이며, 세 번째 인수는 생성할 디스크립터의 개수를 의미합니다. 우리는 한 개의 스토리지 버퍼 디스크립터만을 생성하므로 poolSize
상수 생성자로 eStorageBuffer
와 1 (생성할 디스크립터 개수)을 전달했습니다.
만일 디스크립터 풀로부터 디스크립터 셋을 해당 개수 한계 이상으로 생성한다면 OutOfPoolMemory 오류를 던질 것이니, 사용할 디스크립터 개수와 디스크립터 셋의 개수를 충분히 어림잡아 만들거나, 여력이 된다면 디스크립터 풀에 대한 추상화와 같은 방법을 사용할 수도 있습니다. 지금으로써는 사용할 디스크립터 1개/디스크립터 셋 1개로 명백하니, 이러한 방법은 사용하지 않겠습니다.
디스크립터 셋 할당하기
디스크립터 풀을 생성했다면, 해당 풀로부터 이전에 만든 디스크립터 셋 레이아웃을 따르는 디스크립터 셋을 할당할 수 있습니다.
Subject: [PATCH] Allocate descriptor set from descriptorPool.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -197,5 +197,11 @@
} };
}();
+ // Allocate descriptor set from descriptor pool.
+ const vk::DescriptorSet descriptorSet = (*device).allocateDescriptorSets(vk::DescriptorSetAllocateInfo {
+ *descriptorPool,
+ *descriptorSetLayout,
+ }).front();
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
디바이스의 allocateDescriptorSets
메서드를 이용해 디스크립터 셋을 할당했습니다. 이 메서드는 인수로 주어진 디스크립터 셋 레이아웃 (여기서는 한 개만이 전달되었으나, 여러 개를 전달할 수 있습니다) 각각에 대응하는 디스크립터 셋의 벡터를 반환합니다. 코드에서는 이 벡터에 front()
메서드를 호출해 맨 앞의 디스크립터 셋만을 변수로 선언했습니다. (어차피 우리는 하나의 디스크립터 셋 레이아웃만을 전달했으므로, 하나의 디스크립터 셋만으로 이루어진 벡터가 반환되겠죠!)
여기서 중요한 사실이 하나 있습니다: vk::DescriptorSet
은 raii
네임스페이스 내에 없습니다! 또 생성이 아닌 할당이라는 용어가 사용됐는데, 이는 디스크립터 셋이 실제로는 독자적인 객체가 아닌 디스크립터 풀이 소유하고 있는 디스크립터 셋이 대한 참조임을 의미합니다. 그렇기에, 디스크립터 풀이 소멸하거나 초기화될 경우 해당 풀에서 할당된 디스크립터 셋 또한 소멸합니다 (댕글링 포인터가 됩니다). 예를 들어, 다음과 같은 코드는 오류입니다.
1
2
3
4
5
6
7
8
9
vk::DescriptorSet descriptorSet = [&] {
vk::raii::DescriptorPool pool { ... };
return (*device).allocateDescriptorSets(vk::DescriptorSetAllocateInfo {
*pool,
...
})[0];
// pool is destroyed at here.
}();
// Now descriptor set is dangling pointer.
이는 마치 디바이스와 큐의 관계와 유사합니다. 큐는 디바이스가 생성되면서 만들어지고, 우리는 단지 getQueue
메서드를 이용해 디바이스가 소유한 큐의 포인터만을 돌려받는 것이니, 디바이스가 소멸되면 큐 또한 같이 소멸할 것입니다. 디바이스는 Vulkan 애플리케이션 전체 생명 주기에 존재하니 소멸한 뒤 큐를 사용할 가능성이 낮으나, 디스크립터 풀은 여러번 생명과 소멸을 반복할 수 있으므로 생명 주기에 대한 이해가 필요합니다.
디스크립터 셋에 버퍼 정보 기록하기
디스크립터 셋을 할당했다면, 이제 버퍼의 정보를 기록할 차례입니다. 어떤 디스크립터 셋에 리소스 정보를 기록하기 위해서는 디바이스의 updateDescriptorSets
메서드를 실행해야 하며, 이 메서드는 기록 정보를 나타내는 vk::WriteDescriptorSet
의 배열과 vk::CopyDescriptorSet
의 배열을 인수로 받습니다. 지금으로썬 vk::WriteDescriptorSet
만을 다루어 보겠습니다.
Subject: [PATCH] Write buffer info to descriptorSet.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -203,5 +203,20 @@
*descriptorSetLayout,
}).front();
+ // Write buffer info to descriptorSet.
+ const vk::DescriptorBufferInfo bufferInfo {
+ *buffer,
+ 0,
+ vk::WholeSize,
+ };
+ (*device).updateDescriptorSets(vk::WriteDescriptorSet {
+ descriptorSet,
+ 0,
+ 0,
+ vk::DescriptorType::eStorageBuffer,
+ {},
+ bufferInfo,
+ }, {});
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
우리는 먼저 buffer
버퍼를 디스크립터 버퍼로 사용할 영역을 정의하는 vk::DescriptorBufferInfo
객체를 생성했습니다. 이전 표기와 마찬가지로 오프셋 0, 크기 vk::WholeSize
(버퍼의 전체 크기를 나타냄)를 전달하여 버퍼 전체 영역이 디스크립터 버퍼로 사용됨을 명시하였습니다.
이후 vk::WriteDescriptorSet
객체를 사용해 디스크립터 셋에 디스크립터 버퍼를 어떻게 기록할 것인지를 명시했습니다. 생성자의 인수는 다음과 같습니다:
dstSet
: 기록 대상 디스크립터 셋.descriptorSet
에 기록합니다.dstBinding
: 기록 대상 디스크립터 셋 바인딩. 우리는 0번째 바인딩에 기록하므로 0을 전달했습니다.dstArrayElement
: 해당 바인딩에 대응하는 디스크립터 배열의 원소 인덱스. 앞서 설명했듯 한 바인딩에 여러 개의 디스크립터를 배열 형식으로 사용할 수 있다고 했습니다. 우리는 하나의 디스크립터만을 사용하므로 0을 전달했습니다.descriptorType
: 디스크립터 타입입니다.eStorageBuffer
를 전달했습니다.imageInfo
: 만일 이미지 디스크립터를 기록하고자 할 경우 이 인수로 전달해야 합니다. 우리는 버퍼 디스크립터를 기록하므로nullptr
로 전달하였습니다 ({}
가nullptr
을 나타냅니다).bufferInfo
: 기록할 버퍼 디스크립터.texelBufferView
(위 코드에는 표시되지 않음): 텍셀 버퍼 뷰 디스크립터를 전달할 때 사용됩니다. 여기서는 사용하지 않았습니다.
마지막으로 이 객체를 디바이스의 updateDescriptorSets
메서드 인수로 전달함으로써, 성공적으로 descriptorSet
의 0번째 바인딩이 buffer
버퍼를 가르키게 하였습니다.
커맨드 버퍼 할당 및 커맨드 기록하기
커맨드 풀 생성하기
파이프라인과 디스크립터 셋 둘 다 준비했으니, 이제 최종적으로 파이프라인을 실행할 차례입니다. 파이프라인을 실행하기 위해서는 기존 디바이스의 메서드와는 별개로 커맨드 버퍼를 만들어서, 커맨드 버퍼에 커맨드를 기록한 후 큐에 제출해야 합니다. 디스크립터 셋과 마찬가지로, 커맨드 버퍼 또한 커맨드 풀로부터 할당될 수 있습니다.
Subject: [PATCH] Create command pool.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -218,5 +218,11 @@
bufferInfo,
}, {});
+ // Create command pool.
+ const vk::raii::CommandPool commandPool { device, vk::CommandPoolCreateInfo {
+ {},
+ computeQueueFamilyIndex,
+ } };
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
커맨드 풀의 생성 정보 구조체로는 큐 패밀리 인덱스가 들어갑니다. 해당 큐 패밀리로부터 커맨드 버퍼가 생성될 수 있으며, 이 커맨드 버퍼는 해당 큐 패밀리에 대응하는 큐로 제출되어야만 합니다 (예를 들어, 서로 다른 두 큐 패밀리로부터 생성된 큐 A와 B가 있고, 각각의 큐 패밀리 인덱스로부터 생성된 커맨드 풀 C와 D가 있을 때, C에서 할당된 커맨드 버퍼는 A로만, D에서 할당된 커맨드 버퍼는 B로만 제출될 수 있습니다). 우리는 컴퓨트 큐 패밀리와 이에 대응하는 컴퓨트 큐가 있으므로, 컴퓨트 큐 패밀리 인덱스를 이용해 커맨드 풀을 생성했습니다.
디스크립터 풀과 다르게, 여기서는 총 생성할 커맨드 버퍼의 개수를 명시하지 않아도 됩니다.
커맨드 버퍼 할당하기
커맨드 풀을 생성했다면 커맨드 버퍼를 할당하는 것은 디스크립터 셋을 할당하는 것과 비슷합니다.
Subject: [PATCH] Allocate command buffer from commandPool.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -224,5 +224,12 @@
computeQueueFamilyIndex,
} };
+ // Allocate command buffer from commandPool.
+ const vk::CommandBuffer commandBuffer = (*device).allocateCommandBuffers(vk::CommandBufferAllocateInfo {
+ *commandPool,
+ vk::CommandBufferLevel::ePrimary,
+ 1,
+ }).front();
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
커맨드 버퍼 할당 정보 구조체 vk::CommandBufferAllocateInfo
생성자 인수는 다음을 의미합니다:
- 커맨드 버퍼를 할당할 커맨드 풀,
commandPool
을 전달했습니다. - 커맨드 버퍼의 레벨. 이 레벨은 primary와 primary가 존재하는데, secondary는 primary 커맨드 버퍼에 종속된, 멀티스레드에서 기록될 수 있는 특수한 커맨드 버퍼입니다. 이 튜토리얼에서는 primary 커맨드 버퍼만을 다룹니다.
- 할당할 커맨드 버퍼의 개수. 이 개수는 커맨드 버퍼에 기록할 커맨드의 개수가 아닙니다. 하나의 커맨드 버퍼는 여러 개의 커맨드를 기록할 수 있으며, 할당해야 할 커맨드 버퍼의 개수는 큐에 제출할 커맨드 버퍼의 개수와 같으므로 우리는 오직 한 개만이 필요합니다.
마찬가지로 디바이스의 allocateCommandBuffers
메서드는 커맨드 버퍼의 벡터를 반환하므로, front
메서드를 호출해 처음 1개만을 commandBuffer
로 선언했습니다.
커맨드 버퍼에 커맨드 기록하기
커맨드 버퍼에 커맨드를 기록하는 과정 이전 커맨드 버퍼는 시작(begin)되어야 하고, 기록 과정 이후 종료(end)되어야 합니다. 이 시작 과정에서 커맨드 버퍼의 용도(vk::CommandBufferUsageFlags
)가 정해집니다. 몇 가지 용도가 있으나, 우리는 그중에서 이 커맨드 버퍼가 큐에 오직 1회 제출된다는 eOneTimeSubmit
열거형을 이용해 커맨드 버퍼를 시작하겠습니다.
Subject: [PATCH] Record commands that invoke computePipeline.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -231,5 +231,12 @@
1,
}).front();
+ // Record commands that invoke computePipeline.
+ commandBuffer.begin({ vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
+ commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline);
+ commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipelineLayout, 0, descriptorSet, {});
+ commandBuffer.dispatch(1024 / 256, 1, 1);
+ commandBuffer.end();
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
커맨드 버퍼를 시작하고 난 후, 커맨드 버퍼에 파이프라인·디스크립터 셋을 바인딩하고, 파이프라인 디스패치 커맨드를 기록했습니다.
bindPipeline
메서드의 첫 번째 인수 파이프라인 바인드 포인트 (pipeline bind point)는 이 파이프라인이 컴퓨트 파이프라인이라는 뜻과 더불어 앞으로 명시적으로 파이프라인 바인드 포인트가 변하지 않는 한 후속 커맨드 역시 이 해당 파이프라인에 종속된다는 뜻입니다.bindDescriptorSets
의 세 번째 인수 0은 네 번째 인수로 전달한 디스크립터 셋 배열 (여기서는 하나만이 사용됨)이 순차적으로 바인딩될 첫 번째 인덱스로, 이 코드에서는 0번째 디스크립터 셋부터 하나만이 바인딩됩니다.- 마지막으로
dispatch
메서드에는 컴퓨트 파이프라인을 실행할 워크 그룹의 개수를 3차원으로 나타냈는데, 우리는 워크 그룹의 크기가 256x1x1이고 전체 작업은 (3차원 기준) 1024x1x1개이므로 워크 그룹은 4x1x1로 생성해야 합니다.
커맨드 버퍼 기록이 완료됐으면 end
메서드를 호출해 기록이 종료됨을 명시하면 됩니다. 기록이 끝났으니 이제 커맨드 버퍼를 큐에 제출하면 됩니다.
커맨드 버퍼를 큐에 제출하기
큐의 submit
메서드를 사용하여 커맨드 버퍼의 배열 (여기서는 1개)를 동시에 제출할 수 있습니다.
Subject: [PATCH] Submit commandBuffer to computeQueue and wait for it to finish.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -238,5 +238,13 @@
commandBuffer.dispatch(1024 / 256, 1, 1);
commandBuffer.end();
+ // Submit commandBuffer to computeQueue and wait for it to finish.
+ computeQueue.submit(vk::SubmitInfo {
+ {},
+ {},
+ commandBuffer,
+ });
+ computeQueue.waitIdle();
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
submit
메서드는 vk::SubmitInfo
라는 이름의 제출 정보 구조체와 선택적으로(optional) 펜스(fence)를 인수로 받습니다. 또 제출 정보 구조체 생성자의 각 인수는 순서대로
- 제출한 커맨드 버퍼가 실행되기 전 신호(signal)되야 할 세마포어(semaphore) 배열 (달리 말하면, 제출한 커맨드가 실행되기 전까지 대기(wait)할 세마포어 배열)
- 위 세마포어가 현재 커맨드 버퍼의 스테이지 실행 이전까지 기다려야 하는 시점 배열
- 제출할 커맨드 버퍼 배열
- 이 커맨드 버퍼의 실행이 종결된 후 신호(signal)될 세마포어 배열
을 명시해야 합니다.
세마포어와 펜스에 대해서는 향후 이어질 동기화(synchronization)를 설명하며 다룰 것이고, 우리가 제출할 커맨드 버퍼는 다른 실행에 종속적으로 이어질 필요가 없으므로 일단은 둘 다 기본 인수(nullptr
을 나타냄)를 전달하겠습니다.
commandBuffer
를 submit
메서드에 전달한 후, (대기 세마포어가 없는 한) 이 명령은 큐가 작업을 수행할 수 있을 시점이 됐을 때 실행될 것입니다. 지금 코드에서는 이전까지 큐에 제출한 명령이 없으므로 아마 바로 실행될 것입니다. 실행되면서 실제 GPU에서는 앞서 기록한 커맨드를 수행하게 됩니다.
Vulkan은 비동기적 실행 모델(asynchronous execution model)을 사용하기 때문에, 위와 같이 GPU에서 명령이 실행되는 동안 submit
메서드는 블로킹(blocking)되지 않습니다. 따라서 우리가 실제로 버퍼의 float
들이 두 배가 됐는지 확인하고 싶다면, 이 큐가 작업을 완료할 때까지 기다려야 합니다. 이는 큐의 waitIdle
메서드에 대응하여, 큐가 모든 제출한 작업을 완료하고 대기 상태(idle)일 때가지 실행 흐름을 블록합니다. 해당 메서드 호출 이후, 우리는 버퍼의 값이 두 배가 됐음을 보증할 수 있습니다.
지금은 간단한 상황이므로 큐를 동기화하는 방법으로
waitIdle
메서드를 사용하지만, 이는 실제로 매우 비효율적인 작업입니다. 실제 큐에는 여러 개의 커맨드 버퍼가 제출된 상태일 수 있습니다. 우리가 어떤 커맨드 버퍼를 실행함으로써 호스트 단에서 정보를 얻어내고 할 경우 큐가 대기 상태일 때까지 블록하는 것은 해당 커맨드 버퍼 제출 이후 제출된 추가적인 커맨드 버퍼까지 모두 실행이 종결될 때까지 블록하는 것이며, 멀티스레드 환경일 경우 블록이 끝나지 않을 수도 있습니다. 이 경우 펜스를 사용하거나, Vulkan 1.2에 추가된 타임라인 세마포어(timeline semaphore)를 이용하여 제출한 커맨드 버퍼 실행이 끝나는 시점까지만 블록하세요.
첫 번째 튜토리얼을 끝내며…
GPU가 올바르게 작업을 수행했는지 확인하기 위해, buffer
버퍼의 데이터에 대응하는 nums
를 예상치와 같은지 비교해봅시다. std::views::iota
을 이용해 오름차순으로 나열된 정수 범위를 만들고, std::views::transform
을 이용해 이 범위의 모든 정수를 입력으로 받아 두 배된 결과값의 범위를 만들 수 있습니다. 이 예상치를 std::ranges::equal
을 이용해 nums
와 비교해봅시다.
Subject: [PATCH] Check if result is expected.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,5 @@
+#include <cassert>
+
import std;
import vulkan_hpp;
@@ -246,5 +248,9 @@
});
computeQueue.waitIdle();
+ // Check if the result is correct.
+ constexpr auto expected = std::views::iota(0, 1024) | std::views::transform([](float x) { return x * 2.f; });
+ assert(std::ranges::equal(nums, expected) && "Unexpected result.");
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file
디버그 모드에서 프로젝트를 빌드하고 실행해보세요 (assert
매크로는 릴리즈 빌드에서는 없는 코드와 마찬가지입니다). 만일 버퍼의 실수들이 두 배 되지 않은 경우 Unexpected result.가 출력되며 비정상 종료할 것입니다.
축하합니다. 이렇게 우리는 첫 번째 Vulkan 애플리케이션을 만들었습니다. 지금은 비록 어떠한 이미지도 삼각형도 없지만, 이 과정은 Vulkan이 표방하는 GPGPU 작업을 대변하는 대표적인 예시입니다. 단순한 이 애플리케이션에도 혼동이 올만한 여러가지 어려운 개념이 포함되니 (디스크립터, 디스크립터 셋, 디스크립터 셋 레이아웃, 디스크립터 셋 레이아웃 바인딩, 디스크립터 풀…), 정상 작동하는데 큰 의의를 두셨으면 좋겠습니다. 이 튜토리얼 소주제를 시작하며 던진 네 물음에 답해보세요:
다음 튜토리얼에는 앞서 예고했듯 본격적으로 “볼 수 있는” 이미지를 다루어 보겠습니다. 이번 튜토리얼의 전체 코드 변경 사항은 다음과 같습니다.
Subject: [PATCH] Check if result is expected.
Submit commandBuffer to computeQueue and wait for it to finish.
Record commands that invoke computePipeline.
Allocate command buffer from commandPool.
Create command pool.
Write buffer info to descriptorSet.
Allocate descriptor set from descriptorPool.
Create descriptor pool.
---
Index: src/main.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main.cpp b/src/main.cpp
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,5 @@
+#include <cassert>
+
import std;
import vulkan_hpp;
@@ -184,5 +186,71 @@
*pipelineLayout,
} };
+ // Create descriptor pool.
+ const vk::raii::DescriptorPool descriptorPool = [&] {
+ constexpr vk::DescriptorPoolSize poolSize {
+ vk::DescriptorType::eStorageBuffer,
+ 1,
+ };
+ return vk::raii::DescriptorPool { device, vk::DescriptorPoolCreateInfo {
+ {},
+ 1,
+ poolSize,
+ } };
+ }();
+
+ // Allocate descriptor set from descriptor pool.
+ const vk::DescriptorSet descriptorSet = (*device).allocateDescriptorSets(vk::DescriptorSetAllocateInfo {
+ *descriptorPool,
+ *descriptorSetLayout,
+ }).front();
+
+ // Write buffer info to descriptorSet.
+ const vk::DescriptorBufferInfo bufferInfo {
+ *buffer,
+ 0,
+ vk::WholeSize,
+ };
+ (*device).updateDescriptorSets(vk::WriteDescriptorSet {
+ descriptorSet,
+ 0,
+ 0,
+ vk::DescriptorType::eStorageBuffer,
+ {},
+ bufferInfo,
+ }, {});
+
+ // Create command pool.
+ const vk::raii::CommandPool commandPool { device, vk::CommandPoolCreateInfo {
+ {},
+ computeQueueFamilyIndex,
+ } };
+
+ // Allocate command buffer from commandPool.
+ const vk::CommandBuffer commandBuffer = (*device).allocateCommandBuffers(vk::CommandBufferAllocateInfo {
+ *commandPool,
+ vk::CommandBufferLevel::ePrimary,
+ 1,
+ }).front();
+
+ // Record commands that invoke computePipeline.
+ commandBuffer.begin({ vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
+ commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline);
+ commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *pipelineLayout, 0, descriptorSet, {});
+ commandBuffer.dispatch(1024 / 256, 1, 1);
+ commandBuffer.end();
+
+ // Submit commandBuffer to computeQueue and wait for it to finish.
+ computeQueue.submit(vk::SubmitInfo {
+ {},
+ {},
+ commandBuffer,
+ });
+ computeQueue.waitIdle();
+
+ // Check if the result is correct.
+ constexpr auto expected = std::views::iota(0, 1024) | std::views::transform([](float x) { return x * 2.f; });
+ assert(std::ranges::equal(nums, expected) && "Unexpected result.");
+
(*device).unmapMemory(*bufferMemory);
}
\ No newline at end of file