Fix potential hang on swapchain inheritance

Introduce a mutex to control access to a swapchain's image statuses,
also only wait for swapchain images marked as "PENDING"

Change-Id: I7bd530ea50eb44cb98ed4f674167d14de4a30d53
Signed-off-by: Ben Davis <ben.davis@arm.com>
This commit is contained in:
Ben Davis 2021-05-27 14:57:53 +01:00 committed by Rosen Zhelev
parent bcd934f2aa
commit feb2445f2a
4 changed files with 48 additions and 13 deletions

View file

@ -60,6 +60,8 @@ swapchain::~swapchain()
VkResult swapchain::create_image(VkImageCreateInfo image_create, wsi::swapchain_image &image) VkResult swapchain::create_image(VkImageCreateInfo image_create, wsi::swapchain_image &image)
{ {
VkResult res = VK_SUCCESS; VkResult res = VK_SUCCESS;
const std::lock_guard<std::recursive_mutex> lock(m_image_status_mutex);
res = m_device_data.disp.CreateImage(m_device, &image_create, nullptr, &image.image); res = m_device_data.disp.CreateImage(m_device, &image_create, nullptr, &image.image);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
@ -131,6 +133,7 @@ void swapchain::present_image(uint32_t pending_index)
void swapchain::destroy_image(wsi::swapchain_image &image) void swapchain::destroy_image(wsi::swapchain_image &image)
{ {
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
if (image.status != wsi::swapchain_image::INVALID) if (image.status != wsi::swapchain_image::INVALID)
{ {
if (image.present_fence != VK_NULL_HANDLE) if (image.present_fence != VK_NULL_HANDLE)
@ -144,8 +147,12 @@ void swapchain::destroy_image(wsi::swapchain_image &image)
m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks()); m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks());
image.image = VK_NULL_HANDLE; image.image = VK_NULL_HANDLE;
} }
image.status = wsi::swapchain_image::INVALID;
} }
image_status_lock.unlock();
if (image.data != nullptr) if (image.data != nullptr)
{ {
auto *data = reinterpret_cast<image_data *>(image.data); auto *data = reinterpret_cast<image_data *>(image.data);
@ -158,7 +165,6 @@ void swapchain::destroy_image(wsi::swapchain_image &image)
image.data = nullptr; image.data = nullptr;
} }
image.status = wsi::swapchain_image::INVALID;
} }
} /* namespace headless */ } /* namespace headless */

View file

@ -84,6 +84,8 @@ void swapchain_base::page_flip_thread()
continue; continue;
} }
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
/* If the descendant has started presenting the queue_present operation has marked the image /* If the descendant has started presenting the queue_present operation has marked the image
* as FREE so we simply release it and continue. */ * as FREE so we simply release it and continue. */
if (sc_images[pending_index].status == swapchain_image::FREE) if (sc_images[pending_index].status == swapchain_image::FREE)
@ -92,6 +94,7 @@ void swapchain_base::page_flip_thread()
m_free_image_semaphore.post(); m_free_image_semaphore.post();
continue; continue;
} }
image_status_lock.unlock();
/* First present of the swapchain. If it has an ancestor, wait until all the pending buffers /* First present of the swapchain. If it has an ancestor, wait until all the pending buffers
* from the ancestor have finished page flipping before we set mode. */ * from the ancestor have finished page flipping before we set mode. */
@ -119,13 +122,15 @@ void swapchain_base::page_flip_thread()
void swapchain_base::unpresent_image(uint32_t presented_index) void swapchain_base::unpresent_image(uint32_t presented_index)
{ {
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
m_swapchain_images[presented_index].status = swapchain_image::FREE; m_swapchain_images[presented_index].status = swapchain_image::FREE;
if (m_descendant != VK_NULL_HANDLE) if (m_descendant != VK_NULL_HANDLE)
{ {
destroy_image(m_swapchain_images[presented_index]); destroy_image(m_swapchain_images[presented_index]);
} }
image_status_lock.unlock();
m_free_image_semaphore.post(); m_free_image_semaphore.post();
} }
@ -283,7 +288,7 @@ void swapchain_base::teardown()
{ {
/* This method will block until all resources associated with this swapchain /* This method will block until all resources associated with this swapchain
* are released. Images in the ACQUIRED or FREE state can be freed * are released. Images in the ACQUIRED or FREE state can be freed
* immediately. For images in the PRESENTED state, we will block until the * immediately. For images in the PENDING state, we will block until the
* presentation engine is finished with them. */ * presentation engine is finished with them. */
int res; int res;
@ -376,6 +381,8 @@ VkResult swapchain_base::acquire_next_image(uint64_t timeout, VkSemaphore semaph
return VK_ERROR_OUT_OF_HOST_MEMORY; return VK_ERROR_OUT_OF_HOST_MEMORY;
} }
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
uint32_t i; uint32_t i;
for (i = 0; i < m_swapchain_images.size(); ++i) for (i = 0; i < m_swapchain_images.size(); ++i)
{ {
@ -389,6 +396,8 @@ VkResult swapchain_base::acquire_next_image(uint64_t timeout, VkSemaphore semaph
assert(i < m_swapchain_images.size()); assert(i < m_swapchain_images.size());
image_status_lock.unlock();
if (VK_NULL_HANDLE != semaphore || VK_NULL_HANDLE != fence) if (VK_NULL_HANDLE != semaphore || VK_NULL_HANDLE != fence)
{ {
VkSubmitInfo submit = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; VkSubmitInfo submit = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
@ -452,6 +461,7 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
{ {
VkResult result; VkResult result;
bool descendent_started_presenting = false; bool descendent_started_presenting = false;
const std::lock_guard<std::recursive_mutex> lock(m_image_status_mutex);
if (m_descendant != VK_NULL_HANDLE) if (m_descendant != VK_NULL_HANDLE)
{ {
@ -505,7 +515,6 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
m_pending_buffer_pool.ring[m_pending_buffer_pool.tail] = image_index; m_pending_buffer_pool.ring[m_pending_buffer_pool.tail] = image_index;
m_pending_buffer_pool.tail = (m_pending_buffer_pool.tail + 1) % m_pending_buffer_pool.size; m_pending_buffer_pool.tail = (m_pending_buffer_pool.tail + 1) % m_pending_buffer_pool.size;
m_page_flip_semaphore.post(); m_page_flip_semaphore.post();
return VK_ERROR_OUT_OF_DATE_KHR; return VK_ERROR_OUT_OF_DATE_KHR;
@ -536,21 +545,21 @@ void swapchain_base::deprecate(VkSwapchainKHR descendant)
void swapchain_base::wait_for_pending_buffers() void swapchain_base::wait_for_pending_buffers()
{ {
int num_acquired_images = 0;
int wait; int wait;
int pending_images = 0;
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
for (auto& img : m_swapchain_images) for (auto& img : m_swapchain_images)
{ {
if (img.status == swapchain_image::ACQUIRED) if (img.status == swapchain_image::PENDING)
{ {
++num_acquired_images; ++pending_images;
} }
} }
/* Once all the pending buffers are flipped, the swapchain should have images wait = pending_images;
* in ACQUIRED (application fails to queue them back for presentation), FREE image_status_lock.unlock();
* and one and only one in PRESENTED. */
wait = m_swapchain_images.size() - num_acquired_images - 1;
while (wait > 0) while (wait > 0)
{ {

View file

@ -185,6 +185,16 @@ protected:
*/ */
sem_t m_start_present_semaphore; sem_t m_start_present_semaphore;
/**
* @brief A mutex to protect access to the statuses of the swapchain's images and
* any code paths that rely on this information. We use a recursive mutex as some
* functions such as 'destroy_image' both change an image's status and are called
* conditionally based on an image's status in some cases. A recursive mutex allows
* these functions to be called both with and without the mutex already locked in the
* same thread.
*/
std::recursive_mutex m_image_status_mutex;
/** /**
* @brief Defines if the pthread_t and sem_t members of the class are defined. * @brief Defines if the pthread_t and sem_t members of the class are defined.
* *

View file

@ -578,9 +578,14 @@ VkResult swapchain::create_image(VkImageCreateInfo image_create_info, swapchain_
return VK_ERROR_OUT_OF_HOST_MEMORY; return VK_ERROR_OUT_OF_HOST_MEMORY;
} }
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
image.data = image_data; image.data = image_data;
image.status = swapchain_image::FREE; image.status = swapchain_image::FREE;
VkResult result = allocate_image(image_create_info, image_data, &image.image); VkResult result = allocate_image(image_create_info, image_data, &image.image);
image_status_lock.unlock();
if (result != VK_SUCCESS) if (result != VK_SUCCESS)
{ {
WSI_LOG_ERROR("Failed to allocate image."); WSI_LOG_ERROR("Failed to allocate image.");
@ -721,6 +726,8 @@ void swapchain::present_image(uint32_t pendingIndex)
void swapchain::destroy_image(swapchain_image &image) void swapchain::destroy_image(swapchain_image &image)
{ {
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
if (image.status != swapchain_image::INVALID) if (image.status != swapchain_image::INVALID)
{ {
if (image.present_fence != VK_NULL_HANDLE) if (image.present_fence != VK_NULL_HANDLE)
@ -734,7 +741,12 @@ void swapchain::destroy_image(swapchain_image &image)
m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks()); m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks());
image.image = VK_NULL_HANDLE; image.image = VK_NULL_HANDLE;
} }
image.status = swapchain_image::INVALID;
} }
image_status_lock.unlock();
if (image.data != nullptr) if (image.data != nullptr)
{ {
auto image_data = reinterpret_cast<wayland_image_data *>(image.data); auto image_data = reinterpret_cast<wayland_image_data *>(image.data);
@ -762,8 +774,6 @@ void swapchain::destroy_image(swapchain_image &image)
m_allocator.destroy(1, image_data); m_allocator.destroy(1, image_data);
image.data = nullptr; image.data = nullptr;
} }
image.status = swapchain_image::INVALID;
} }
bool swapchain::free_image_found() bool swapchain::free_image_found()