parent
422d54e387
commit
64ba099002
4 changed files with 77 additions and 59 deletions
|
|
@ -103,15 +103,16 @@ public:
|
|||
// Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan.
|
||||
|
||||
// Semaphores are used to coordinate operations within the graphics queue and ensure correct command ordering
|
||||
std::array<VkSemaphore, MAX_CONCURRENT_FRAMES> presentCompleteSemaphores{};
|
||||
std::array<VkSemaphore, MAX_CONCURRENT_FRAMES> renderCompleteSemaphores{};
|
||||
std::vector<VkSemaphore> presentCompleteSemaphores{};
|
||||
std::vector<VkSemaphore> renderCompleteSemaphores{};
|
||||
|
||||
VkCommandPool commandPool{ VK_NULL_HANDLE };
|
||||
std::array<VkCommandBuffer, MAX_CONCURRENT_FRAMES> commandBuffers{};
|
||||
std::array<VkFence, MAX_CONCURRENT_FRAMES> waitFences{};
|
||||
|
||||
// To select the correct sync objects, we need to keep track of the current frame
|
||||
// To select the correct sync and command objects, we need to keep track of the current frame and (swapchain) image index
|
||||
uint32_t currentFrame{ 0 };
|
||||
uint32_t currentSemaphore{ 0 };
|
||||
|
||||
VulkanExample() : VulkanExampleBase()
|
||||
{
|
||||
|
|
@ -130,25 +131,26 @@ public:
|
|||
{
|
||||
// Clean up used Vulkan resources
|
||||
// Note: Inherited destructor cleans up resources stored in base class
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
||||
|
||||
vkDestroyBuffer(device, vertices.buffer, nullptr);
|
||||
vkFreeMemory(device, vertices.memory, nullptr);
|
||||
|
||||
vkDestroyBuffer(device, indices.buffer, nullptr);
|
||||
vkFreeMemory(device, indices.memory, nullptr);
|
||||
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
|
||||
for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) {
|
||||
vkDestroyFence(device, waitFences[i], nullptr);
|
||||
vkDestroySemaphore(device, presentCompleteSemaphores[i], nullptr);
|
||||
vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
|
||||
vkDestroyBuffer(device, uniformBuffers[i].buffer, nullptr);
|
||||
vkFreeMemory(device, uniformBuffers[i].memory, nullptr);
|
||||
if (device) {
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
||||
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
||||
vkDestroyBuffer(device, vertices.buffer, nullptr);
|
||||
vkFreeMemory(device, vertices.memory, nullptr);
|
||||
vkDestroyBuffer(device, indices.buffer, nullptr);
|
||||
vkFreeMemory(device, indices.memory, nullptr);
|
||||
vkDestroyCommandPool(device, commandPool, nullptr);
|
||||
for (size_t i = 0; i < presentCompleteSemaphores.size(); i++) {
|
||||
vkDestroySemaphore(device, presentCompleteSemaphores[i], nullptr);
|
||||
}
|
||||
for (size_t i = 0; i < presentCompleteSemaphores.size(); i++) {
|
||||
vkDestroySemaphore(device, renderCompleteSemaphores[i], nullptr);
|
||||
}
|
||||
for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) {
|
||||
vkDestroyFence(device, waitFences[i], nullptr);
|
||||
vkDestroyBuffer(device, uniformBuffers[i].buffer, nullptr);
|
||||
vkFreeMemory(device, uniformBuffers[i].memory, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,24 +180,25 @@ public:
|
|||
// Create the per-frame (in flight) Vulkan synchronization primitives used in this example
|
||||
void createSynchronizationPrimitives()
|
||||
{
|
||||
// Semaphores are used for correct command ordering within a queue
|
||||
VkSemaphoreCreateInfo semaphoreCI{};
|
||||
semaphoreCI.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
|
||||
// Fences are used to check draw command buffer completion on the host
|
||||
VkFenceCreateInfo fenceCI{};
|
||||
fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
// Create the fences in signaled state (so we don't wait on first render of each command buffer)
|
||||
fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
|
||||
for (uint32_t i = 0; i < MAX_CONCURRENT_FRAMES; i++) {
|
||||
VkFenceCreateInfo fenceCI{};
|
||||
fenceCI.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
// Create the fences in signaled state (so we don't wait on first render of each command buffer)
|
||||
fenceCI.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
// Fence used to ensure that command buffer has completed exection before using it again
|
||||
VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFences[i]));
|
||||
}
|
||||
// Semaphores are per swapchain image
|
||||
presentCompleteSemaphores.resize(swapChain.images.size());
|
||||
renderCompleteSemaphores.resize(swapChain.images.size());
|
||||
for (size_t i = 0; i < swapChain.images.size(); i++) {
|
||||
// Semaphores are used for correct command ordering within a queue
|
||||
VkSemaphoreCreateInfo semaphoreCI{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||
// Semaphore used to ensure that image presentation is complete before starting to submit again
|
||||
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &presentCompleteSemaphores[i]));
|
||||
// Semaphore used to ensure that all commands submitted have been finished before submitting the image to the queue
|
||||
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCI, nullptr, &renderCompleteSemaphores[i]));
|
||||
|
||||
// Fence used to ensure that command buffer has completed exection before using it again
|
||||
VK_CHECK_RESULT(vkCreateFence(device, &fenceCI, nullptr, &waitFences[i]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -912,7 +915,7 @@ public:
|
|||
// Get the next swap chain image from the implementation
|
||||
// Note that the implementation is free to return the images in any order, so we must use the acquire function and can't just cycle through the images/imageIndex on our own
|
||||
uint32_t imageIndex;
|
||||
VkResult result = vkAcquireNextImageKHR(device, swapChain.swapChain, UINT64_MAX, presentCompleteSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
|
||||
VkResult result = vkAcquireNextImageKHR(device, swapChain.swapChain, UINT64_MAX, presentCompleteSemaphores[currentSemaphore], VK_NULL_HANDLE, &imageIndex);
|
||||
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
windowResize();
|
||||
return;
|
||||
|
|
@ -1008,10 +1011,10 @@ public:
|
|||
submitInfo.commandBufferCount = 1; // We submit a single command buffer
|
||||
|
||||
// Semaphore to wait upon before the submitted command buffer starts executing
|
||||
submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentFrame];
|
||||
submitInfo.pWaitSemaphores = &presentCompleteSemaphores[currentSemaphore];
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
// Semaphore to be signaled when command buffers have completed
|
||||
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[currentFrame];
|
||||
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[currentSemaphore];
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
|
||||
// Submit to the graphics queue passing a wait fence
|
||||
|
|
@ -1024,7 +1027,7 @@ public:
|
|||
VkPresentInfoKHR presentInfo{};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
presentInfo.pWaitSemaphores = &renderCompleteSemaphores[currentFrame];
|
||||
presentInfo.pWaitSemaphores = &renderCompleteSemaphores[currentSemaphore];
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapChain.swapChain;
|
||||
presentInfo.pImageIndices = &imageIndex;
|
||||
|
|
@ -1039,6 +1042,8 @@ public:
|
|||
|
||||
// Select the next frame to render to, based on the max. no. of concurrent frames
|
||||
currentFrame = (currentFrame + 1) % MAX_CONCURRENT_FRAMES;
|
||||
// Similar for the semaphores, which need to be unique to the swapchain images
|
||||
currentSemaphore = (currentSemaphore + 1) % swapChain.imageCount;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue