Vulkan: Swapchain synchronization

This PR adds swapchain synchronization. When the swapchain swaps the
buffers it can add a wait semaphore/signal semaphore to support GPU
based synchronization

10 times playback of `rain_restaurant.blend` on AMD RX 7700
Before: 10 × Animation playback: 72347.5540 ms, average: 7234.75539684 ms
After: 10 × Animation playback: 41523.2441 ms, average: 4152.32441425 ms

Getting around the OpenGL performance target.

Pull Request: https://projects.blender.org/blender/blender/pulls/136259
This commit is contained in:
Jeroen Bakker
2025-03-24 10:28:52 +01:00
parent fa6e104362
commit 409ce2b976
7 changed files with 146 additions and 32 deletions

View File

@@ -746,6 +746,10 @@ typedef struct {
VkSurfaceFormatKHR surface_format;
/** Resolution of the image. */
VkExtent2D extent;
/** Semaphore to wait before updating the image. */
VkSemaphore acquire_semaphore;
/** Semaphore to signal after the image has been updated. */
VkSemaphore present_semaphore;
} GHOST_VulkanSwapChainData;
typedef struct {

View File

@@ -485,7 +485,7 @@ GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
m_command_buffer(VK_NULL_HANDLE),
m_surface(VK_NULL_HANDLE),
m_swapchain(VK_NULL_HANDLE),
m_fence(VK_NULL_HANDLE)
m_render_frame(0)
{
}
@@ -523,10 +523,16 @@ GHOST_TSuccess GHOST_ContextVK::destroySwapchain()
if (m_swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(device, m_swapchain, nullptr);
}
if (m_fence != VK_NULL_HANDLE) {
vkDestroyFence(device, m_fence, nullptr);
m_fence = VK_NULL_HANDLE;
VK_CHECK(vkDeviceWaitIdle(device));
for (VkSemaphore semaphore : m_acquire_semaphores) {
vkDestroySemaphore(device, semaphore, nullptr);
}
m_acquire_semaphores.clear();
for (VkSemaphore semaphore : m_present_semaphores) {
vkDestroySemaphore(device, semaphore, nullptr);
}
m_present_semaphores.clear();
return GHOST_kSuccess;
}
@@ -562,21 +568,27 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
* swapchain image. Other do it when calling vkQueuePresent. */
VkResult result = VK_ERROR_OUT_OF_DATE_KHR;
uint32_t image_index = 0;
int32_t render_frame = 0;
while (result == VK_ERROR_OUT_OF_DATE_KHR) {
result = vkAcquireNextImageKHR(
device, m_swapchain, UINT64_MAX, VK_NULL_HANDLE, m_fence, &image_index);
render_frame = (m_render_frame + 1) % m_acquire_semaphores.size();
result = vkAcquireNextImageKHR(device,
m_swapchain,
UINT64_MAX,
m_acquire_semaphores[render_frame],
VK_NULL_HANDLE,
&image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
destroySwapchain();
createSwapchain();
}
}
VK_CHECK(vkWaitForFences(device, 1, &m_fence, VK_TRUE, UINT64_MAX));
VK_CHECK(vkResetFences(device, 1, &m_fence));
GHOST_VulkanSwapChainData swap_chain_data;
swap_chain_data.image = m_swapchain_images[image_index];
swap_chain_data.surface_format = m_surface_format;
swap_chain_data.extent = m_render_extent;
swap_chain_data.acquire_semaphore = m_acquire_semaphores[render_frame];
swap_chain_data.present_semaphore = m_present_semaphores[render_frame];
if (swap_buffers_pre_callback_) {
swap_buffers_pre_callback_(&swap_chain_data);
@@ -584,8 +596,8 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 0;
present_info.pWaitSemaphores = nullptr;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &m_present_semaphores[render_frame];
present_info.swapchainCount = 1;
present_info.pSwapchains = &m_swapchain;
present_info.pImageIndices = &image_index;
@@ -887,10 +899,17 @@ GHOST_TSuccess GHOST_ContextVK::createSwapchain()
vkGetSwapchainImagesKHR(device, m_swapchain, &image_count, nullptr);
m_swapchain_images.resize(image_count);
vkGetSwapchainImagesKHR(device, m_swapchain, &image_count, m_swapchain_images.data());
VkFenceCreateInfo fence_info = {};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VK_CHECK(vkCreateFence(device, &fence_info, nullptr, &m_fence));
const VkSemaphoreCreateInfo vk_semaphore_create_info = {
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
m_acquire_semaphores.resize(image_count);
m_present_semaphores.resize(image_count);
for (int index = 0; index < image_count; index++) {
VK_CHECK(vkCreateSemaphore(
device, &vk_semaphore_create_info, nullptr, &m_acquire_semaphores[index]));
VK_CHECK(vkCreateSemaphore(
device, &vk_semaphore_create_info, nullptr, &m_present_semaphores[index]));
}
m_render_frame = 0;
/* Change image layout from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR. */
VkCommandBufferBeginInfo begin_info = {};

View File

@@ -183,11 +183,13 @@ class GHOST_ContextVK : public GHOST_Context {
VkSurfaceKHR m_surface;
VkSwapchainKHR m_swapchain;
std::vector<VkImage> m_swapchain_images;
std::vector<VkSemaphore> m_acquire_semaphores;
std::vector<VkSemaphore> m_present_semaphores;
uint32_t m_render_frame;
VkExtent2D m_render_extent;
VkExtent2D m_render_extent_min;
VkSurfaceFormatKHR m_surface_format;
VkFence m_fence;
std::function<void(const GHOST_VulkanSwapChainData *)> swap_buffers_pre_callback_;
std::function<void(void)> swap_buffers_post_callback_;