2 * Copyright (c) 2017-2021 Arm Limited.
4 * SPDX-License-Identifier: MIT
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 * @file swapchain_base.cpp
28 * @brief Contains the implementation for the swapchain.
30 * This file contains much of the swapchain implementation,
31 * that is not specific to how images are created or presented.
41 #include <vulkan/vulkan.h>
43 #include "swapchain_base.hpp"
45 #if VULKAN_WSI_DEBUG > 0
46 #define WSI_PRINT_ERROR(...) fprintf(stderr, ##__VA_ARGS__)
48 #define WSI_PRINT_ERROR(...) (void)0
54 void swapchain_base::page_flip_thread()
56 auto &sc_images = m_swapchain_images;
57 VkResult vk_res = VK_SUCCESS;
58 uint64_t timeout = UINT64_MAX;
59 constexpr uint64_t SEMAPHORE_TIMEOUT = 250000000; /* 250 ms. */
61 /* No mutex is needed for the accesses to m_page_flip_thread_run variable as after the variable is
62 * initialized it is only ever changed to false. The while loop will make the thread read the
63 * value repeatedly, and the combination of semaphores and thread joins will force any changes to
64 * the variable to be visible to this thread.
66 while (m_page_flip_thread_run)
68 /* Waiting for the page_flip_semaphore which will be signalled once there is an
70 if ((vk_res = m_page_flip_semaphore.wait(SEMAPHORE_TIMEOUT)) == VK_TIMEOUT)
72 /* Image is not ready yet. */
75 assert(vk_res == VK_SUCCESS);
77 /* We want to present the oldest queued for present image from our present queue,
78 * which we can find at the sc->pending_buffer_pool.head index. */
79 uint32_t pending_index = m_pending_buffer_pool.ring[m_pending_buffer_pool.head];
80 m_pending_buffer_pool.head = (m_pending_buffer_pool.head + 1) % m_pending_buffer_pool.size;
82 /* We wait for the fence of the oldest pending image to be signalled. */
83 vk_res = m_device_data.disp.WaitForFences(m_device, 1, &sc_images[pending_index].present_fence, VK_TRUE,
85 if (vk_res != VK_SUCCESS)
88 m_free_image_semaphore.post();
92 /* If the descendant has started presenting the queue_present operation has marked the image
93 * as FREE so we simply release it and continue. */
94 if (sc_images[pending_index].status == swapchain_image::FREE)
96 destroy_image(sc_images[pending_index]);
97 m_free_image_semaphore.post();
101 /* First present of the swapchain. If it has an ancestor, wait until all the pending buffers
102 * from the ancestor have finished page flipping before we set mode. */
105 if (m_ancestor != VK_NULL_HANDLE)
107 auto *ancestor = reinterpret_cast<swapchain_base *>(m_ancestor);
108 ancestor->wait_for_pending_buffers();
111 sem_post(&m_start_present_semaphore);
113 present_image(pending_index);
115 m_first_present = false;
117 /* The swapchain has already started presenting. */
120 present_image(pending_index);
125 void swapchain_base::unpresent_image(uint32_t presented_index)
127 m_swapchain_images[presented_index].status = swapchain_image::FREE;
129 if (m_descendant != VK_NULL_HANDLE)
131 destroy_image(m_swapchain_images[presented_index]);
134 m_free_image_semaphore.post();
137 swapchain_base::swapchain_base(layer::device_private_data &dev_data, const VkAllocationCallbacks *callbacks)
138 : m_device_data(dev_data)
139 , m_page_flip_thread_run(true)
140 , m_thread_sem_defined(false)
141 , m_first_present(true)
142 , m_pending_buffer_pool{ nullptr, 0, 0, 0 }
143 , m_allocator(callbacks, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT)
144 , m_swapchain_images(m_allocator)
145 , m_surface(VK_NULL_HANDLE)
146 , m_present_mode(VK_PRESENT_MODE_IMMEDIATE_KHR)
147 , m_descendant(VK_NULL_HANDLE)
148 , m_ancestor(VK_NULL_HANDLE)
149 , m_device(VK_NULL_HANDLE)
150 , m_queue(VK_NULL_HANDLE)
154 VkResult swapchain_base::init(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info)
156 assert(device != VK_NULL_HANDLE);
157 assert(swapchain_create_info != nullptr);
158 assert(swapchain_create_info->surface != VK_NULL_HANDLE);
164 m_surface = swapchain_create_info->surface;
166 /* Check presentMode has a compatible value with swapchain - everything else should be taken care at image creation.*/
167 static const std::array<VkPresentModeKHR, 2> present_modes = { VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR };
168 bool present_mode_found = false;
169 for (uint32_t i = 0; i < present_modes.size() && !present_mode_found; i++)
171 if (swapchain_create_info->presentMode == present_modes[i])
173 present_mode_found = true;
177 if (!present_mode_found)
179 return VK_ERROR_INITIALIZATION_FAILED;
182 /* Init image to invalid values. */
183 if (!m_swapchain_images.try_resize(swapchain_create_info->minImageCount))
184 return VK_ERROR_OUT_OF_HOST_MEMORY;
186 /* Initialize ring buffer. */
187 m_pending_buffer_pool.ring = m_allocator.create<uint32_t>(m_swapchain_images.size(), 0);
188 if (m_pending_buffer_pool.ring == nullptr)
190 return VK_ERROR_OUT_OF_HOST_MEMORY;
193 m_pending_buffer_pool.head = 0;
194 m_pending_buffer_pool.tail = 0;
195 m_pending_buffer_pool.size = m_swapchain_images.size();
197 /* We have allocated images, we can call the platform init function if something needs to be done. */
198 result = init_platform(device, swapchain_create_info);
199 if (result != VK_SUCCESS)
204 VkImageCreateInfo image_create_info = {};
205 image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
206 image_create_info.pNext = nullptr;
207 image_create_info.imageType = VK_IMAGE_TYPE_2D;
208 image_create_info.format = swapchain_create_info->imageFormat;
209 image_create_info.extent = { swapchain_create_info->imageExtent.width, swapchain_create_info->imageExtent.height, 1 };
210 image_create_info.mipLevels = 1;
211 image_create_info.arrayLayers = swapchain_create_info->imageArrayLayers;
212 image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
213 image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
214 image_create_info.usage = swapchain_create_info->imageUsage;
215 image_create_info.flags = 0;
216 image_create_info.sharingMode = swapchain_create_info->imageSharingMode;
217 image_create_info.queueFamilyIndexCount = swapchain_create_info->queueFamilyIndexCount;
218 image_create_info.pQueueFamilyIndices = swapchain_create_info->pQueueFamilyIndices;
219 image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
221 result = m_free_image_semaphore.init(m_swapchain_images.size());
222 if (result != VK_SUCCESS)
224 assert(result == VK_ERROR_OUT_OF_HOST_MEMORY);
228 for (auto& img : m_swapchain_images)
230 result = create_image(image_create_info, img);
231 if (result != VK_SUCCESS)
237 m_device_data.disp.GetDeviceQueue(m_device, 0, 0, &m_queue);
238 result = m_device_data.SetDeviceLoaderData(m_device, m_queue);
239 if (VK_SUCCESS != result)
244 /* Setup semaphore for signaling pageflip thread */
245 result = m_page_flip_semaphore.init(0);
246 if (result != VK_SUCCESS)
251 res = sem_init(&m_start_present_semaphore, 0, 0);
252 /* Only programming error can cause this to fail. */
256 return VK_ERROR_OUT_OF_HOST_MEMORY;
259 m_thread_sem_defined = true;
261 /* Launch page flipping thread */
262 m_page_flip_thread = std::thread(&swapchain_base::page_flip_thread, this);
264 /* Release the swapchain images of the old swapchain in order
265 * to free up memory for new swapchain. This is necessary especially
266 * on platform with limited display memory size.
268 * NB: This must be done last in initialization, when the rest of
269 * the swapchain is valid.
271 if (swapchain_create_info->oldSwapchain != VK_NULL_HANDLE)
274 m_ancestor = swapchain_create_info->oldSwapchain;
276 auto *ancestor = reinterpret_cast<swapchain_base *>(m_ancestor);
277 ancestor->deprecate(reinterpret_cast<VkSwapchainKHR>(this));
285 void swapchain_base::teardown()
287 /* This method will block until all resources associated with this swapchain
288 * are released. Images in the ACQUIRED or FREE state can be freed
289 * immediately. For images in the PRESENTED state, we will block until the
290 * presentation engine is finished with them. */
293 bool descendent_started_presenting = false;
295 if (m_descendant != VK_NULL_HANDLE)
297 auto *desc = reinterpret_cast<swapchain_base *>(m_descendant);
298 for (auto& img : desc->m_swapchain_images)
300 if (img.status == swapchain_image::PRESENTED ||
301 img.status == swapchain_image::PENDING)
303 /* Here we wait for the start_present_semaphore, once this semaphore is up,
304 * the descendant has finished waiting, we don't want to delete vkImages and vkFences
305 * and semaphores before the waiting is done. */
306 sem_wait(&desc->m_start_present_semaphore);
308 descendent_started_presenting = true;
314 /* If descendant started presenting, there is no pending buffer in the swapchain. */
315 if (m_is_valid && descendent_started_presenting == false)
317 wait_for_pending_buffers();
320 if (m_queue != VK_NULL_HANDLE)
322 /* Make sure the vkFences are done signaling. */
323 m_device_data.disp.QueueWaitIdle(m_queue);
326 /* We are safe to destroy everything. */
327 if (m_thread_sem_defined)
329 /* Tell flip thread to end. */
330 m_page_flip_thread_run = false;
332 if (m_page_flip_thread.joinable())
334 m_page_flip_thread.join();
338 WSI_PRINT_ERROR("m_page_flip_thread is not joinable");
341 res = sem_destroy(&m_start_present_semaphore);
344 WSI_PRINT_ERROR("sem_destroy failed for start_present_semaphore with %d\n", errno);
348 if (m_descendant != VK_NULL_HANDLE)
350 auto *sc = reinterpret_cast<swapchain_base *>(m_descendant);
351 sc->clear_ancestor();
354 if (m_ancestor != VK_NULL_HANDLE)
356 auto *sc = reinterpret_cast<swapchain_base *>(m_ancestor);
357 sc->clear_descendant();
359 /* Release the images array. */
360 for (auto& img : m_swapchain_images)
362 /* Call implementation specific release */
366 m_allocator.destroy(m_swapchain_images.size(), m_pending_buffer_pool.ring);
369 VkResult swapchain_base::acquire_next_image(uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *image_index)
371 VkResult retval = wait_for_free_buffer(timeout);
372 if (retval != VK_SUCCESS)
379 return VK_ERROR_OUT_OF_HOST_MEMORY;
383 for (i = 0; i < m_swapchain_images.size(); ++i)
385 if (m_swapchain_images[i].status == swapchain_image::FREE)
387 m_swapchain_images[i].status = swapchain_image::ACQUIRED;
393 assert(i < m_swapchain_images.size());
395 if (VK_NULL_HANDLE != semaphore || VK_NULL_HANDLE != fence)
397 VkSubmitInfo submit = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
399 if (VK_NULL_HANDLE != semaphore)
401 submit.signalSemaphoreCount = 1;
402 submit.pSignalSemaphores = &semaphore;
405 submit.commandBufferCount = 0;
406 submit.pCommandBuffers = nullptr;
407 retval = m_device_data.disp.QueueSubmit(m_queue, 1, &submit, fence);
408 assert(retval == VK_SUCCESS);
414 VkResult swapchain_base::get_swapchain_images(uint32_t *swapchain_image_count, VkImage *swapchain_images)
416 if (swapchain_images == nullptr)
418 /* Return the number of swapchain images. */
419 *swapchain_image_count = m_swapchain_images.size();
425 assert(m_swapchain_images.size() > 0);
426 assert(*swapchain_image_count > 0);
428 /* Populate array, write actual number of images returned. */
429 uint32_t current_image = 0;
433 swapchain_images[current_image] = m_swapchain_images[current_image].image;
437 if (current_image == m_swapchain_images.size())
439 *swapchain_image_count = current_image;
444 } while (current_image < *swapchain_image_count);
446 /* If swapchain_image_count is smaller than the number of presentable images
447 * in the swapchain, VK_INCOMPLETE must be returned instead of VK_SUCCESS. */
448 *swapchain_image_count = current_image;
450 return VK_INCOMPLETE;
454 VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *present_info, const uint32_t image_index)
457 bool descendent_started_presenting = false;
459 if (m_descendant != VK_NULL_HANDLE)
461 auto *desc = reinterpret_cast<swapchain_base *>(m_descendant);
462 for (auto& img : desc->m_swapchain_images)
464 if (img.status == swapchain_image::PRESENTED ||
465 img.status == swapchain_image::PENDING)
467 descendent_started_presenting = true;
473 /* When the semaphore that comes in is signalled, we know that all work is done. So, we do not
474 * want to block any future Vulkan queue work on it. So, we pass in BOTTOM_OF_PIPE bit as the
477 VkPipelineStageFlags pipeline_stage_flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
479 VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO,
481 present_info->waitSemaphoreCount,
482 present_info->pWaitSemaphores,
483 &pipeline_stage_flags,
489 assert(m_swapchain_images[image_index].status == swapchain_image::ACQUIRED);
490 result = m_device_data.disp.ResetFences(m_device, 1, &m_swapchain_images[image_index].present_fence);
491 if (result != VK_SUCCESS)
496 result = m_device_data.disp.QueueSubmit(queue, 1, &submit_info, m_swapchain_images[image_index].present_fence);
497 if (result != VK_SUCCESS)
502 /* If the descendant has started presenting, we should release the image
503 * however we do not want to block inside the main thread so we mark it
504 * as free and let the page flip thread take care of it. */
505 if (descendent_started_presenting)
507 m_swapchain_images[image_index].status = swapchain_image::FREE;
509 m_pending_buffer_pool.ring[m_pending_buffer_pool.tail] = image_index;
510 m_pending_buffer_pool.tail = (m_pending_buffer_pool.tail + 1) % m_pending_buffer_pool.size;
512 m_page_flip_semaphore.post();
514 return VK_ERROR_OUT_OF_DATE_KHR;
517 m_swapchain_images[image_index].status = swapchain_image::PENDING;
519 m_pending_buffer_pool.ring[m_pending_buffer_pool.tail] = image_index;
520 m_pending_buffer_pool.tail = (m_pending_buffer_pool.tail + 1) % m_pending_buffer_pool.size;
522 m_page_flip_semaphore.post();
526 void swapchain_base::deprecate(VkSwapchainKHR descendant)
528 for (auto& img : m_swapchain_images)
530 if (img.status == swapchain_image::FREE)
536 /* Set its descendant. */
537 m_descendant = descendant;
540 void swapchain_base::wait_for_pending_buffers()
542 int num_acquired_images = 0;
545 for (auto& img : m_swapchain_images)
547 if (img.status == swapchain_image::ACQUIRED)
549 ++num_acquired_images;
553 /* Once all the pending buffers are flipped, the swapchain should have images
554 * in ACQUIRED (application fails to queue them back for presentation), FREE
555 * and one and only one in PRESENTED. */
556 wait = m_swapchain_images.size() - num_acquired_images - 1;
560 /* Take down one free image semaphore. */
561 wait_for_free_buffer(UINT64_MAX);
566 void swapchain_base::clear_ancestor()
568 m_ancestor = VK_NULL_HANDLE;
571 void swapchain_base::clear_descendant()
573 m_descendant = VK_NULL_HANDLE;
576 VkResult swapchain_base::wait_for_free_buffer(uint64_t timeout)
579 /* first see if a buffer is already marked as free */
580 retval = m_free_image_semaphore.wait(0);
581 if (retval == VK_NOT_READY)
583 /* if not, we still have work to do even if timeout==0 -
584 * the swapchain implementation may be able to get a buffer without
587 retval = get_free_buffer(&timeout);
588 if (retval == VK_SUCCESS)
590 /* the sub-implementation has done it's thing, so re-check the
592 retval = m_free_image_semaphore.wait(timeout);
599 #undef WSI_PRINT_ERROR
601 } /* namespace wsi */