diff options
Diffstat (limited to 'wsi')
-rw-r--r-- | wsi/headless/surface_properties.cpp | 165 | ||||
-rw-r--r-- | wsi/headless/surface_properties.hpp | 49 | ||||
-rw-r--r-- | wsi/headless/swapchain.cpp | 178 | ||||
-rw-r--r-- | wsi/headless/swapchain.hpp | 94 | ||||
-rw-r--r-- | wsi/surface_properties.hpp | 63 | ||||
-rw-r--r-- | wsi/swapchain_base.cpp | 684 | ||||
-rw-r--r-- | wsi/swapchain_base.hpp | 361 |
7 files changed, 1594 insertions, 0 deletions
diff --git a/wsi/headless/surface_properties.cpp b/wsi/headless/surface_properties.cpp new file mode 100644 index 0000000..a3f69b9 --- /dev/null +++ b/wsi/headless/surface_properties.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <algorithm> +#include <array> +#include <cassert> +#include <cstdlib> +#include <map> +#include <mutex> + +#include <vulkan/vk_icd.h> +#include <vulkan/vulkan.h> + +#include <layer/private_data.hpp> + +#include "surface_properties.hpp" + +#define UNUSED(x) ((void)(x)) + +namespace wsi +{ +namespace headless +{ + +VkResult surface_properties::get_surface_capabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR *surface_capabilities) +{ + UNUSED(surface); + /* Image count limits */ + surface_capabilities->minImageCount = 1; + /* There is no maximum theoretically speaking */ + surface_capabilities->maxImageCount = UINT32_MAX; + + /* Surface extents */ + surface_capabilities->currentExtent = { 0xffffffff, 0xffffffff }; + surface_capabilities->minImageExtent = { 1, 1 }; + /* Ask the device for max */ + VkPhysicalDeviceProperties dev_props; + layer::instance_private_data::get(layer::get_key(physical_device)).disp.GetPhysicalDeviceProperties(physical_device, &dev_props); + + surface_capabilities->maxImageExtent = { dev_props.limits.maxImageDimension2D, + dev_props.limits.maxImageDimension2D }; + surface_capabilities->maxImageArrayLayers = 1; + + /* Surface transforms */ + surface_capabilities->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + surface_capabilities->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + + /* Composite alpha */ + surface_capabilities->supportedCompositeAlpha = static_cast<VkCompositeAlphaFlagBitsKHR>( + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR | VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR | + VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR | VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR); + + /* Image usage flags */ + surface_capabilities->supportedUsageFlags = + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + + return VK_SUCCESS; +} + +VkResult surface_properties::get_surface_formats(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *surface_format_count, VkSurfaceFormatKHR *surface_formats) +{ + UNUSED(surface); + + VkResult res = VK_SUCCESS; + /* Construct a list of all formats supported by the driver - for color attachment */ + VkFormat formats[VK_FORMAT_RANGE_SIZE]; + uint32_t format_count = 0; + + for (int id = 0; id < VK_FORMAT_RANGE_SIZE; id++) + { + VkImageFormatProperties image_format_props; + + res = layer::instance_private_data::get(layer::get_key(physical_device)) + .disp.GetPhysicalDeviceImageFormatProperties( + physical_device, static_cast<VkFormat>(id), VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, &image_format_props); + + if (res != VK_ERROR_FORMAT_NOT_SUPPORTED) + { + formats[format_count] = static_cast<VkFormat>(id); + format_count++; + } + } + assert(format_count > 0); + assert(surface_format_count != nullptr); + res = VK_SUCCESS; + if (nullptr == surface_formats) + { + *surface_format_count = format_count; + } + else + { + if (format_count > *surface_format_count) + { + res = VK_INCOMPLETE; + } + + *surface_format_count = std::min(*surface_format_count, format_count); + for (uint32_t i = 0; i < *surface_format_count; ++i) + { + surface_formats[i].format = formats[i]; + surface_formats[i].colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + } + } + + return res; +} + +VkResult surface_properties::get_surface_present_modes(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *present_mode_count, VkPresentModeKHR *present_modes) +{ + UNUSED(physical_device); + UNUSED(surface); + + VkResult res = VK_SUCCESS; + static const std::array<VkPresentModeKHR, 2> modes = { VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR }; + + assert(present_mode_count != nullptr); + + if (nullptr == present_modes) + { + *present_mode_count = modes.size(); + } + else + { + if (modes.size() > *present_mode_count) + { + res = VK_INCOMPLETE; + } + *present_mode_count = std::min(*present_mode_count, static_cast<uint32_t>(modes.size())); + for (uint32_t i = 0; i < *present_mode_count; ++i) + { + present_modes[i] = modes[i]; + } + } + + return res; +} + +} /* namespace headless */ +} /* namespace wsi */ diff --git a/wsi/headless/surface_properties.hpp b/wsi/headless/surface_properties.hpp new file mode 100644 index 0000000..2f97f26 --- /dev/null +++ b/wsi/headless/surface_properties.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include <vulkan/vk_icd.h> +#include <vulkan/vulkan.h> +#include <wsi/surface_properties.hpp> + +namespace wsi +{ +namespace headless +{ + +struct surface_properties +{ + static VkResult get_surface_capabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR *pSurfaceCapabilities); + + static VkResult get_surface_formats(VkPhysicalDevice physical_device, VkSurfaceKHR surface, uint32_t *surfaceFormatCount, + VkSurfaceFormatKHR *surfaceFormats); + + static VkResult get_surface_present_modes(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *pPresentModeCount, VkPresentModeKHR *pPresentModes); +}; + +} /* namespace headless */ +} /* namespace wsi */ diff --git a/wsi/headless/swapchain.cpp b/wsi/headless/swapchain.cpp new file mode 100644 index 0000000..4f09886 --- /dev/null +++ b/wsi/headless/swapchain.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file swapchain.cpp + * + * @brief Contains the implementation for a headless swapchain. + */ + +#include <cassert> +#include <cstdlib> + +#include <util/timed_semaphore.hpp> + +#include "swapchain.hpp" + +namespace wsi +{ +namespace headless +{ + +struct image_data +{ + /* Device memory backing the image. */ + VkDeviceMemory memory; +}; + +swapchain::swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator) + : wsi::swapchain_base(dev_data, pAllocator) +{ +} + +swapchain::~swapchain() +{ + /* Call the base's teardown */ + teardown(); +} + +VkResult swapchain::create_image(const VkImageCreateInfo &image_create, wsi::swapchain_image &image) +{ + VkResult res = VK_SUCCESS; + res = m_device_data.disp.CreateImage(m_device, &image_create, nullptr, &image.image); + assert(VK_SUCCESS == res); + + VkMemoryRequirements memory_requirements; + m_device_data.disp.GetImageMemoryRequirements(m_device, image.image, &memory_requirements); + + /* Find a memory type */ + size_t mem_type_idx = 0; + for (; mem_type_idx < 8 * sizeof(memory_requirements.memoryTypeBits); ++mem_type_idx) + { + if (memory_requirements.memoryTypeBits & (1u << mem_type_idx)) + { + break; + } + } + + assert(mem_type_idx <= 8 * sizeof(memory_requirements.memoryTypeBits) - 1); + + VkMemoryAllocateInfo mem_info = {}; + mem_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + mem_info.allocationSize = memory_requirements.size; + mem_info.memoryTypeIndex = mem_type_idx; + image_data *data = nullptr; + + /* Create image_data */ + if (m_alloc_callbacks != nullptr) + { + data = static_cast<image_data *>(m_alloc_callbacks->pfnAllocation( + m_alloc_callbacks->pUserData, sizeof(image_data), 0, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT)); + } + else + { + data = static_cast<image_data *>(malloc(sizeof(image_data))); + } + + if (data == nullptr) + { + m_device_data.disp.DestroyImage(m_device, image.image, m_alloc_callbacks); + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + image.data = reinterpret_cast<void *>(data); + image.status = wsi::swapchain_image::FREE; + + res = m_device_data.disp.AllocateMemory(m_device, &mem_info, nullptr, &data->memory); + assert(VK_SUCCESS == res); + if (res != VK_SUCCESS) + { + destroy_image(image); + return res; + } + + res = m_device_data.disp.BindImageMemory(m_device, image.image, data->memory, 0); + assert(VK_SUCCESS == res); + if (res != VK_SUCCESS) + { + destroy_image(image); + return res; + } + + /* Initialize presentation fence. */ + VkFenceCreateInfo fence_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, 0 }; + res = m_device_data.disp.CreateFence(m_device, &fence_info, nullptr, &image.present_fence); + if (res != VK_SUCCESS) + { + destroy_image(image); + return res; + } + return res; +} + +void swapchain::present_image(uint32_t pending_index) +{ + unpresent_image(pending_index); +} + +void swapchain::destroy_image(wsi::swapchain_image &image) +{ + if (image.status != wsi::swapchain_image::INVALID) + { + if (image.present_fence != VK_NULL_HANDLE) + { + m_device_data.disp.DestroyFence(m_device, image.present_fence, nullptr); + image.present_fence = VK_NULL_HANDLE; + } + + if (image.image != VK_NULL_HANDLE) + { + m_device_data.disp.DestroyImage(m_device, image.image, m_alloc_callbacks); + image.image = VK_NULL_HANDLE; + } + } + + if (image.data != nullptr) + { + auto *data = reinterpret_cast<image_data *>(image.data); + if (data->memory != VK_NULL_HANDLE) + { + m_device_data.disp.FreeMemory(m_device, data->memory, nullptr); + data->memory = VK_NULL_HANDLE; + } + if (m_alloc_callbacks != nullptr) + { + m_alloc_callbacks->pfnFree(m_alloc_callbacks->pUserData, data); + } + else + { + free(data); + } + image.data = nullptr; + } + + image.status = wsi::swapchain_image::INVALID; +} + +} /* namespace headless */ +} /* namespace wsi */ diff --git a/wsi/headless/swapchain.hpp b/wsi/headless/swapchain.hpp new file mode 100644 index 0000000..c25d5c7 --- /dev/null +++ b/wsi/headless/swapchain.hpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file swapchain.hpp + * + * @brief Contains the class definition for a headless swapchain. + */ + +#pragma once + +#include <vulkan/vk_icd.h> +#include <vulkan/vulkan.h> +#include <wsi/swapchain_base.hpp> + +namespace wsi +{ +namespace headless +{ + +/** + * @brief Headless swapchain class. + * + * This class is mostly empty, because all the swapchain stuff is handled by the swapchain class, + * which we inherit. This class only provides a way to create an image and page-flip ops. + */ +class swapchain : public wsi::swapchain_base +{ +public: + explicit swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator); + + ~swapchain(); + +protected: + /** + * @brief Platform specific init + */ + VkResult init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *pSwapchainCreateInfo) + { + return VK_SUCCESS; + }; + + /** + * @brief Creates a new swapchain image. + * + * @param image_create_info Data to be used to create the image. + * + * @param image Handle to the image. + * + * @return If image creation is successful returns VK_SUCCESS, otherwise + * will return VK_ERROR_OUT_OF_DEVICE_MEMORY or VK_ERROR_INITIALIZATION_FAILED + * depending on the error that occured. + */ + VkResult create_image(const VkImageCreateInfo &image_create_info, wsi::swapchain_image &image); + + /** + * @brief Method to perform a present - just calls unpresent_image on headless + * + * @param pendingIndex Index of the pending image to be presented. + * + */ + void present_image(uint32_t pendingIndex); + + /** + * @brief Method to release a swapchain image + * + * @param image Handle to the image about to be released. + */ + void destroy_image(wsi::swapchain_image &image); +}; + +} /* namespace headless */ +} /* namespace wsi */ diff --git a/wsi/surface_properties.hpp b/wsi/surface_properties.hpp new file mode 100644 index 0000000..b4b585b --- /dev/null +++ b/wsi/surface_properties.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file surface_properties.hpp + * + * @brief Vulkan WSI surface query interfaces. + */ + +#pragma once + +#include <vulkan/vulkan.h> + +namespace wsi +{ + +/** + * @brief The base surface property query interface. + */ +template <typename T> +struct surface_properties +{ + static VkResult get_surface_capabilities(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + VkSurfaceCapabilitiesKHR *surface_capabilities) + { + return T::get_surface_capabilities(physical_device, surface, surface_capabilities); + } + + static VkResult get_surface_formats(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *surface_format_count, VkSurfaceFormatKHR *surface_formats) + { + return T::get_surface_formats(physical_device, surface, surface_format_count, surface_formats); + } + + static VkResult get_surface_present_modes(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *present_mode_count, VkPresentModeKHR *present_modes) + { + return T::get_surface_present_modes(physical_device, surface, present_mode_count, present_modes); + } +}; + +} /* namespace wsi */ diff --git a/wsi/swapchain_base.cpp b/wsi/swapchain_base.cpp new file mode 100644 index 0000000..a9731b7 --- /dev/null +++ b/wsi/swapchain_base.cpp @@ -0,0 +1,684 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file swapchain_base.cpp + * + * @brief Contains the implementation for the swapchain. + * + * This file contains much of the swapchain implementation, + * that is not specific to how images are created or presented. + */ + +#include <array> +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstdlib> + +#include <unistd.h> +#include <vulkan/vulkan.h> + +#include "swapchain_base.hpp" + +#if VULKAN_WSI_DEBUG > 0 +#define WSI_PRINT_ERROR(...) fprintf(stderr, ##__VA_ARGS__) +#else +#define WSI_PRINT_ERROR(...) (void)0 +#endif + +namespace wsi +{ + +/** + * @brief Per swapchain thread function that handles page flipping. + * This thread should be running for the lifetime of the swapchain. + * The thread simply calls the implementation's present_image() method. + * There are 3 main cases we cover here: + * + * 1. On the first present of the swapchain if the swapchain has + * an ancestor we must wait for it to finish presenting. + * 2. The normal use case where we do page flipping, in this + * case change the currently PRESENTED image with the oldest + * PENDING image. + * 3. If the enqueued image is marked as FREE it means the + * descendant of the swapchain has started presenting so we + * should release the image and continue. + * + * The function always waits on the page_flip_semaphore of the + * swapchain. Once it passes that we must wait for the fence of the + * oldest pending image to be signalled, this means that the gpu has + * finished rendering to it and we can present it. From there on the + * logic splits into the above 3 cases and if an image has been + * presented then the old one is marked as FREE and the free_image + * semaphore of the swapchain will be posted. + **/ +__attribute__((noreturn)) void *page_flip_thread(void *ptr) +{ + auto *sc = static_cast<swapchain_base *>(ptr); + wsi::swapchain_image *sc_images = sc->m_swapchain_images; + VkResult vk_res = VK_SUCCESS; + uint64_t timeout = UINT64_MAX; + + while (true) + { + /* Waiting for the page_flip_semaphore which will be signalled once there is an + * image to display.*/ + sem_wait(&sc->m_page_flip_semaphore); + + /* We want to present the oldest queued for present image from our present queue, + * which we can find at the sc->pending_buffer_pool.head index. */ + uint32_t pending_index = sc->m_pending_buffer_pool.ring[sc->m_pending_buffer_pool.head]; + sc->m_pending_buffer_pool.head = (sc->m_pending_buffer_pool.head + 1) % sc->m_pending_buffer_pool.size; + + /* We wait for the fence of the oldest pending image to be signalled. */ + vk_res = sc->m_device_data.disp.WaitForFences(sc->m_device, 1, &sc_images[pending_index].present_fence, VK_TRUE, + timeout); + if (vk_res != VK_SUCCESS) + { + sc->m_is_valid = false; + sc->m_free_image_semaphore.post(); + continue; + } + + /* If the descendant has started presenting the queue_present operation has marked the image + * as FREE so we simply release it and continue. */ + if (sc_images[pending_index].status == swapchain_image::FREE) + { + sc->destroy_image(sc_images[pending_index]); + sc->m_free_image_semaphore.post(); + continue; + } + + /* 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. */ + if (sc->m_first_present) + { + if (sc->m_ancestor != VK_NULL_HANDLE) + { + auto *ancestor = reinterpret_cast<swapchain_base *>(sc->m_ancestor); + ancestor->wait_for_pending_buffers(); + } + + sem_post(&sc->m_start_present_semaphore); + + sc->present_image(pending_index); + + sc->m_first_present = false; + } + /* The swapchain has already started presenting. */ + else + { + sc->present_image(pending_index); + } + } +} + +void swapchain_base::unpresent_image(uint32_t presented_index) +{ + m_swapchain_images[presented_index].status = swapchain_image::FREE; + + if (m_descendant != VK_NULL_HANDLE) + { + destroy_image(m_swapchain_images[presented_index]); + } + + m_free_image_semaphore.post(); +} + +swapchain_base::swapchain_base(layer::device_private_data &dev_data, const VkAllocationCallbacks *allocator) + : m_device_data(dev_data) + , m_thread_sem_defined(false) + , m_first_present(true) + , m_pending_buffer_pool{ nullptr, 0, 0, 0 } + , m_num_swapchain_images(0) + , m_swapchain_images(nullptr) + , m_alloc_callbacks(allocator) + , m_surface(VK_NULL_HANDLE) + , m_present_mode(VK_PRESENT_MODE_IMMEDIATE_KHR) + , m_descendant(VK_NULL_HANDLE) + , m_ancestor(VK_NULL_HANDLE) + , m_device(VK_NULL_HANDLE) + , m_queue(VK_NULL_HANDLE) +{ +} + +VkResult swapchain_base::init(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info) +{ + assert(device != VK_NULL_HANDLE); + assert(swapchain_create_info != nullptr); + assert(swapchain_create_info->surface != VK_NULL_HANDLE); + + int res; + VkResult result; + + m_device = device; + m_surface = swapchain_create_info->surface; + + /* Check presentMode has a compatible value with swapchain - everything else should be taken care at image creation.*/ + static const std::array<VkPresentModeKHR, 2> present_modes = { VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR }; + bool present_mode_found = false; + for (uint32_t i = 0; i < present_modes.size() && !present_mode_found; i++) + { + if (swapchain_create_info->presentMode == present_modes[i]) + { + present_mode_found = true; + } + } + + if (!present_mode_found) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + m_num_swapchain_images = swapchain_create_info->minImageCount; + + size_t images_alloc_size = sizeof(swapchain_image) * m_num_swapchain_images; + if (m_alloc_callbacks != nullptr) + { + m_swapchain_images = static_cast<swapchain_image *>(m_alloc_callbacks->pfnAllocation( + m_alloc_callbacks->pUserData, images_alloc_size, alignof(swapchain_image), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT)); + } + else + { + m_swapchain_images = static_cast<swapchain_image *>(malloc(images_alloc_size)); + } + + if (m_swapchain_images == nullptr) + { + m_num_swapchain_images = 0; + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + /* We have allocated images, we can call the platform init function if something needs to be done. */ + result = init_platform(device, swapchain_create_info); + if (result != VK_SUCCESS) + { + return result; + } + + for (uint32_t i = 0; i < m_num_swapchain_images; ++i) + { + /* Init image to invalid values. */ + m_swapchain_images[i].image = VK_NULL_HANDLE; + m_swapchain_images[i].present_fence = VK_NULL_HANDLE; + m_swapchain_images[i].status = swapchain_image::INVALID; + m_swapchain_images[i].data = nullptr; + } + + /* Initialize ring buffer. */ + if (m_alloc_callbacks != nullptr) + { + m_pending_buffer_pool.ring = static_cast<uint32_t *>( + m_alloc_callbacks->pfnAllocation(m_alloc_callbacks->pUserData, sizeof(uint32_t) * m_num_swapchain_images, + alignof(uint32_t), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT)); + } + else + { + m_pending_buffer_pool.ring = static_cast<uint32_t *>(malloc(sizeof(uint32_t) * m_num_swapchain_images)); + } + + if (m_pending_buffer_pool.ring == nullptr) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + m_pending_buffer_pool.head = 0; + m_pending_buffer_pool.tail = 0; + m_pending_buffer_pool.size = m_num_swapchain_images; + + VkImageCreateInfo image_create_info = {}; + image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_create_info.pNext = nullptr; + image_create_info.imageType = VK_IMAGE_TYPE_2D; + image_create_info.format = swapchain_create_info->imageFormat; + image_create_info.extent = { swapchain_create_info->imageExtent.width, swapchain_create_info->imageExtent.height, 1 }; + image_create_info.mipLevels = 1; + image_create_info.arrayLayers = swapchain_create_info->imageArrayLayers; + image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_create_info.usage = swapchain_create_info->imageUsage; + image_create_info.flags = 0; + image_create_info.sharingMode = swapchain_create_info->imageSharingMode; + image_create_info.queueFamilyIndexCount = swapchain_create_info->queueFamilyIndexCount; + image_create_info.pQueueFamilyIndices = swapchain_create_info->pQueueFamilyIndices; + image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + result = m_free_image_semaphore.init(m_num_swapchain_images); + if (result != VK_SUCCESS) + { + assert(result == VK_ERROR_OUT_OF_HOST_MEMORY); + return result; + } + + for (unsigned i = 0; i < m_num_swapchain_images; i++) + { + result = create_image(image_create_info, m_swapchain_images[i]); + if (result != VK_SUCCESS) + { + return result; + } + } + + m_device_data.disp.GetDeviceQueue(m_device, 0, 0, &m_queue); + result = m_device_data.SetDeviceLoaderData(m_device, m_queue); + if (VK_SUCCESS != result) + { + return result; + } + + /* Setup semaphore for signaling pageflip thread */ + res = sem_init(&m_page_flip_semaphore, 0, 0); + + /* Only programming error can cause this to fail. */ + assert(res == 0); + if (res != 0) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + res = sem_init(&m_start_present_semaphore, 0, 0); + /* Only programming error can cause this to fail. */ + assert(res == 0); + if (res != 0) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + m_thread_sem_defined = true; + + /* Launch page flipping thread */ + res = pthread_create(&m_page_flip_thread, NULL, page_flip_thread, static_cast<void *>(this)); + if (res < 0) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + /* Release the swapchain images of the old swapchain in order + * to free up memory for new swapchain. This is necessary especially + * on platform with limited display memory size. + * + * NB: This must be done last in initialization, when the rest of + * the swapchain is valid. + */ + if (swapchain_create_info->oldSwapchain != VK_NULL_HANDLE) + { + /* Set ancestor. */ + m_ancestor = swapchain_create_info->oldSwapchain; + + auto *ancestor = reinterpret_cast<swapchain_base *>(m_ancestor); + ancestor->deprecate(reinterpret_cast<VkSwapchainKHR>(this)); + } + + m_is_valid = true; + + return VK_SUCCESS; +} + +void swapchain_base::teardown() +{ + /* This method will block until all resources associated with this swapchain + * are released. Images in the ACQUIRED or FREE state can be freed + * immediately. For images in the PRESENTED state, we will block until the + * presentation engine is finished with them. */ + + int res; + bool descendent_started_presenting = false; + + if (m_descendant != VK_NULL_HANDLE) + { + auto *desc = reinterpret_cast<swapchain_base *>(m_descendant); + for (uint32_t i = 0; i < desc->m_num_swapchain_images; ++i) + { + if (desc->m_swapchain_images[i].status == swapchain_image::PRESENTED || + desc->m_swapchain_images[i].status == swapchain_image::PENDING) + { + /* Here we wait for the start_present_semaphore, once this semaphore is up, + * the descendant has finished waiting, we don't want to delete vkImages and vkFences + * and semaphores before the waiting is done. */ + sem_wait(&desc->m_start_present_semaphore); + + descendent_started_presenting = true; + break; + } + } + } + + /* If descendant started presenting, there is no pending buffer in the swapchain. */ + if (descendent_started_presenting == false) + { + wait_for_pending_buffers(); + } + + /* Make sure the vkFences are done signaling. */ + m_device_data.disp.QueueWaitIdle(m_queue); + + /* We are safe to destroy everything. */ + + if (m_thread_sem_defined) + { + res = pthread_cancel(m_page_flip_thread); + if (res != 0) + { + WSI_PRINT_ERROR("pthread_cancel failed for page_flip_thread %lu with %d\n", m_page_flip_thread, res); + } + + res = pthread_join(m_page_flip_thread, NULL); + if (res != 0) + { + WSI_PRINT_ERROR("pthread_join failed for page_flip_thread %lu with %d\n", m_page_flip_thread, res); + } + + res = sem_destroy(&m_page_flip_semaphore); + if (res != 0) + { + WSI_PRINT_ERROR("sem_destroy failed for page_flip_semaphore with %d\n", errno); + } + + res = sem_destroy(&m_start_present_semaphore); + if (res != 0) + { + WSI_PRINT_ERROR("sem_destroy failed for start_present_semaphore with %d\n", errno); + } + } + + if (m_descendant != VK_NULL_HANDLE) + { + auto *sc = reinterpret_cast<swapchain_base *>(m_descendant); + sc->clear_ancestor(); + } + + if (m_ancestor != VK_NULL_HANDLE) + { + auto *sc = reinterpret_cast<swapchain_base *>(m_ancestor); + sc->clear_descendant(); + } + + /* Release the images array. */ + if (m_swapchain_images != nullptr) + { + + for (uint32_t i = 0; i < m_num_swapchain_images; ++i) + { + /* Call implementation specific release */ + destroy_image(m_swapchain_images[i]); + } + + if (m_alloc_callbacks != nullptr) + { + m_alloc_callbacks->pfnFree(m_alloc_callbacks->pUserData, m_swapchain_images); + } + else + { + free(m_swapchain_images); + } + } + + /* Free ring buffer. */ + if (m_pending_buffer_pool.ring != nullptr) + { + if (m_alloc_callbacks != nullptr) + { + m_alloc_callbacks->pfnFree(m_alloc_callbacks->pUserData, m_pending_buffer_pool.ring); + } + else + { + free(m_pending_buffer_pool.ring); + } + } +} + +VkResult swapchain_base::acquire_next_image(uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *image_index) +{ + VkResult retval = wait_for_free_buffer(timeout); + if (retval != VK_SUCCESS) + { + return retval; + } + + if (!m_is_valid) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + uint32_t i; + for (i = 0; i < m_num_swapchain_images; ++i) + { + if (m_swapchain_images[i].status == swapchain_image::FREE) + { + m_swapchain_images[i].status = swapchain_image::ACQUIRED; + *image_index = i; + break; + } + } + + assert(i < m_num_swapchain_images); + + if (VK_NULL_HANDLE != semaphore || VK_NULL_HANDLE != fence) + { + VkSubmitInfo submit = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; + + if (VK_NULL_HANDLE != semaphore) + { + submit.signalSemaphoreCount = 1; + submit.pSignalSemaphores = &semaphore; + } + + submit.commandBufferCount = 0; + submit.pCommandBuffers = nullptr; + retval = m_device_data.disp.QueueSubmit(m_queue, 1, &submit, fence); + assert(retval == VK_SUCCESS); + } + + return retval; +} + +VkResult swapchain_base::get_swapchain_images(uint32_t *swapchain_image_count, VkImage *swapchain_images) +{ + if (swapchain_images == nullptr) + { + /* Return the number of swapchain images. */ + *swapchain_image_count = m_num_swapchain_images; + + return VK_SUCCESS; + } + else + { + assert(m_num_swapchain_images > 0); + assert(*swapchain_image_count > 0); + + /* Populate array, write actual number of images returned. */ + uint32_t current_image = 0; + + do + { + swapchain_images[current_image] = m_swapchain_images[current_image].image; + + current_image++; + + if (current_image == m_num_swapchain_images) + { + *swapchain_image_count = current_image; + + return VK_SUCCESS; + } + + } while (current_image < *swapchain_image_count); + + /* If swapchain_image_count is smaller than the number of presentable images + * in the swapchain, VK_INCOMPLETE must be returned instead of VK_SUCCESS. */ + *swapchain_image_count = current_image; + + return VK_INCOMPLETE; + } +} + +VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *present_info, const uint32_t image_index) +{ + VkResult result; + bool descendent_started_presenting = false; + + if (m_descendant != VK_NULL_HANDLE) + { + auto *desc = reinterpret_cast<swapchain_base *>(m_descendant); + for (uint32_t i = 0; i < desc->m_num_swapchain_images; ++i) + { + if (desc->m_swapchain_images[i].status == swapchain_image::PRESENTED || + desc->m_swapchain_images[i].status == swapchain_image::PENDING) + { + descendent_started_presenting = true; + break; + } + } + } + + /* When the semaphore that comes in is signalled, we know that all work is done. So, we do not + * want to block any future Vulkan queue work on it. So, we pass in BOTTOM_OF_PIPE bit as the + * wait flag. + */ + VkPipelineStageFlags pipeline_stage_flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + + VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO, + NULL, + present_info->waitSemaphoreCount, + present_info->pWaitSemaphores, + &pipeline_stage_flags, + 0, + NULL, + 0, + NULL }; + + assert(m_swapchain_images[image_index].status == swapchain_image::ACQUIRED); + result = m_device_data.disp.ResetFences(m_device, 1, &m_swapchain_images[image_index].present_fence); + if (result != VK_SUCCESS) + { + return result; + } + + result = m_device_data.disp.QueueSubmit(queue, 1, &submit_info, m_swapchain_images[image_index].present_fence); + if (result != VK_SUCCESS) + { + return result; + } + + /* If the descendant has started presenting, we should release the image + * however we do not want to block inside the main thread so we mark it + * as free and let the page flip thread take care of it. */ + if (descendent_started_presenting) + { + m_swapchain_images[image_index].status = swapchain_image::FREE; + + 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; + + sem_post(&m_page_flip_semaphore); + + return VK_ERROR_OUT_OF_DATE_KHR; + } + + m_swapchain_images[image_index].status = swapchain_image::PENDING; + + 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; + + sem_post(&m_page_flip_semaphore); + + return VK_SUCCESS; +} + +void swapchain_base::deprecate(VkSwapchainKHR descendant) +{ + for (unsigned i = 0; i < m_num_swapchain_images; i++) + { + if (m_swapchain_images[i].status == swapchain_image::FREE) + { + destroy_image(m_swapchain_images[i]); + } + } + + /* Set its descendant. */ + m_descendant = descendant; +} + +void swapchain_base::wait_for_pending_buffers() +{ + int num_acquired_images = 0; + int wait; + + for (uint32_t i = 0; i < m_num_swapchain_images; ++i) + { + if (m_swapchain_images[i].status == swapchain_image::ACQUIRED) + { + ++num_acquired_images; + } + } + + /* Once all the pending buffers are flipped, the swapchain should have images + * in ACQUIRED (application fails to queue them back for presentation), FREE + * and one and only one in PRESENTED. */ + wait = m_num_swapchain_images - num_acquired_images - 1; + + while (wait > 0) + { + /* Take down one free image semaphore. */ + wait_for_free_buffer(UINT64_MAX); + --wait; + } +} + +void swapchain_base::clear_ancestor() +{ + m_ancestor = VK_NULL_HANDLE; +} + +void swapchain_base::clear_descendant() +{ + m_descendant = VK_NULL_HANDLE; +} + +VkResult swapchain_base::wait_for_free_buffer(uint64_t timeout) +{ + VkResult retval; + /* first see if a buffer is already marked as free */ + retval = m_free_image_semaphore.wait(0); + if (retval == VK_NOT_READY) + { + /* if not, we still have work to do even if timeout==0 - + * the swapchain implementation may be able to get a buffer without + * waiting */ + + retval = get_free_buffer(&timeout); + if (retval == VK_SUCCESS) + { + /* the sub-implementation has done it's thing, so re-check the + * semaphore */ + retval = m_free_image_semaphore.wait(timeout); + } + } + + return retval; +} + +#undef WSI_PRINT_ERROR + +} /* namespace wsi */ diff --git a/wsi/swapchain_base.hpp b/wsi/swapchain_base.hpp new file mode 100644 index 0000000..1b827a3 --- /dev/null +++ b/wsi/swapchain_base.hpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2017-2019 Arm Limited. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file swapchain_base.hpp + * + * @brief Contains the class definition for a base swapchain. + */ + +#pragma once + +#include <pthread.h> +#include <semaphore.h> +#include <vulkan/vulkan.h> + +#include <layer/private_data.hpp> +#include <util/timed_semaphore.hpp> + +namespace wsi +{ + +/* Forward declare the page flip thread function so we can befriend it. */ +void *page_flip_thread(void *ptr); + +struct swapchain_image +{ + enum status + { + INVALID, + ACQUIRED, + PENDING, + PRESENTED, + FREE, + }; + + /* Implementation specific data */ + void *data; + + VkImage image; + status status; + + VkFence present_fence; +}; + +/** + * @brief Base swapchain class + * + * - the swapchain implementations inherit from this class. + * - the VkSwapchain will hold a pointer to this class. + * - much of the swapchain implementation is done by this class, as the only things needed + * in the implementation are how to create a presentable image and how to present an image. + */ +class swapchain_base +{ +public: + swapchain_base(layer::device_private_data &dev_data, const VkAllocationCallbacks *allocator); + + virtual ~swapchain_base() + { + /* nop */ + } + + /** + * @brief Create swapchain. + * + * Perform all swapchain initialization, create presentable images etc. + */ + VkResult init(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info); + + /** + * @brief Acquires a free image. + * + * Current implementation blocks until a free image is available. + * + * @param timeout Unused since we block until a free image is available. + * + * @param semaphore A semaphore signaled once an image is acquired. + * + * @param fence A fence signaled once an image is acquired. + * + * @param pImageIndex The index of the acquired image. + * + * @return VK_SUCCESS on completion. + */ + VkResult acquire_next_image(uint64_t timeout, VkSemaphore semaphore, VkFence fence, uint32_t *image_index); + + /** + * @brief Gets the number of swapchain images or a number of at most + * m_num_swapchain_images images. + * + * @param pSwapchainImageCount Used to return number of images in + * the swapchain if second parameter is nullptr or represents the + * number of images to be returned in second parameter. + * + * @param pSwapchainImage Array of VkImage handles. + * + * @return If number of requested images is less than the number of available + * images in the swapchain returns VK_INCOMPLETE otherwise VK_SUCCESS. + */ + VkResult get_swapchain_images(uint32_t *swapchain_image_count, VkImage *swapchain_image); + + /** + * @brief Submits a present request for the supplied image. + * + * @param queue The queue to which the submission will be made to. + * + * @param pPresentInfo Information about the swapchain and image to be presented. + * + * @param imageIndex The index of the image to be presented. + * + * @return If queue submission fails returns error of vkQueueSubmit, if the + * swapchain has a descendant who started presenting returns VK_ERROR_OUT_OF_DATE_KHR, + * otherwise returns VK_SUCCESS. + */ + VkResult queue_present(VkQueue queue, const VkPresentInfoKHR *present_info, const uint32_t image_index); + +protected: + friend void *page_flip_thread(void *ptr); + + layer::device_private_data &m_device_data; + + /** + * @brief Handle to the page flip thread. + */ + pthread_t m_page_flip_thread; + + /** + * @brief In case we encounter threading or drm errors we need a way to + * notify the user of the failure. When this flag is false, acquire_next_image + * will return an error code. + */ + bool m_is_valid; + + struct ring_buffer + { + /* Ring buffer to hold the image indexes. */ + uint32_t *ring; + /* Head of the ring. */ + uint32_t head; + /* End of the ring. */ + uint32_t tail; + /* Size of the ring. */ + uint32_t size; + }; + /** + * @brief A semaphore to be signalled once a page flip even occurs. + */ + sem_t m_page_flip_semaphore; + + /** + * @brief A semaphore to be signalled once the swapchain has one frame on screen. + */ + sem_t m_start_present_semaphore; + + /** + * @brief Defines if the pthread_t and sem_t members of the class are defined. + * + * As they are opaque types theer's no known invalid value that we ca initialize to, + * and therefore determine if we need to cleanup. + */ + bool m_thread_sem_defined; + + /** + * @brief A flag to track if it is the first present for the chain. + */ + bool m_first_present; + + /** + * @brief In order to present the images in a FIFO order we implement + * a ring buffer to hold the images queued for presentation. Since the + * two pointers (head and tail) are used by different + * therads and we do not allow the application to acquire more images + * than we have we eliminate race conditions. + */ + ring_buffer m_pending_buffer_pool; + + /** + * @brief The number of swapchain images. + */ + uint32_t m_num_swapchain_images; + + /** + * @brief Array of images in the swapchain. + */ + swapchain_image *m_swapchain_images; + + /** + * @brief User provided memory allocation callbacks. + */ + const VkAllocationCallbacks *m_alloc_callbacks; + + /** + * @brief Handle to the surface object this swapchain will present images to. + */ + VkSurfaceKHR m_surface; + + /** + * @brief present mode to use for this swapchain + */ + VkPresentModeKHR m_present_mode; + + /** + * @brief Descendant of this swapchain. + * Used to check whether or not a descendant of this swapchain has started + * presenting images to the surface already. If it has, any calls to queuePresent + * for this swapchain will return VK_ERROR_OUT_OF_DATE_KHR. + */ + VkSwapchainKHR m_descendant; + + /** + * @brief Ancestor of this swapchain. + * Used to check whether the ancestor swapchain has completed all of its + * pending page flips (this is required before this swapchain presents for the + * first time. + */ + VkSwapchainKHR m_ancestor; + + /** + * @brief Handle to the logical device the swapchain is created for. + */ + VkDevice m_device; + + /** + * @brief Handle to the queue used for signalling submissions + */ + VkQueue m_queue; + + /* + * @brief Method to wait on all pending buffers to be displayed. + */ + void wait_for_pending_buffers(); + + /** + * @brief Remove cached ancestor. + */ + void clear_ancestor(); + + /** + * @brief Remove cached descendant. + */ + void clear_descendant(); + + /** + * @brief Deprecate this swapchain. + * + * If an application replaces an old swapchain with a new one, the older swapchain + * needs to be deprecated. This method releases all the FREE images and sets the + * descendant of the swapchain. We do not need to care about images in other states + * at this point since they will be released by the page flip thread. + * + * @param descendant Handle to the descendant swapchain. + */ + void deprecate(VkSwapchainKHR descendant); + + /** + * @brief Platform specific initialization + */ + virtual VkResult init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info) = 0; + + /** + * @brief Base swapchain teardown. + * + * Even though the inheritance gives us a nice way to defer display specific allocation + * and presentation outside of the base class, it however robs the children classes - which + * also happen to do some of their state setting - the oppurtunity to do the last clean up + * call, as the base class' destructor is called at the end. This method provides a way to do it. + * The destructor is a virtual function and much of the swapchain teardown happens in this method + * which gets called from the child's destructor. + */ + void teardown(); + + /** + * @brief Creates a new swapchain image. + * + * @param image_create_info Data to be used to create the image. + * + * @param image Handle to the image. + * + * @return If image creation is successful returns VK_SUCCESS, otherwise + * will return VK_ERROR_OUT_OF_DEVICE_MEMORY or VK_ERROR_INITIALIZATION_FAILED + * depending on the error that occured. + */ + virtual VkResult create_image(const VkImageCreateInfo &image_create_info, swapchain_image &image) = 0; + + /** + * @brief Method to present and image + * + * @param pending_index Index of the pending image to be presented. + * + */ + virtual void present_image(uint32_t pending_index) = 0; + + /** + * @brief Transition a presented image to free. + * + * Called by swapchain implementation when a new image has been presented. + * + * @param presented_index Index of the image to be marked as free. + */ + void unpresent_image(uint32_t presented_index); + + /** + * @brief Method to release a swapchain image + * + * @param image Handle to the image about to be released. + */ + virtual void destroy_image(swapchain_image &image){}; + + /** + * @brief Hook for any actions to free up a buffer for acquire + * + * @param[in,out] timeout time to wait, in nanoseconds. 0 doesn't block, + * UINT64_MAX waits indefinately. The timeout should + * be updated if a sleep is required - this can + * be set to 0 if the semaphore is now not expected + * block. + */ + virtual VkResult get_free_buffer(uint64_t *timeout) + { + return VK_SUCCESS; + } + +private: + /** + * @brief Wait for a buffer to become free. + */ + VkResult wait_for_free_buffer(uint64_t timeout); + + /** + * @brief A semaphore to be signalled once a free image becomes available. + * + * Uses a custom semaphore implementation that uses a condition variable. + * it is slower, but has a safe timedwait implementation. + * + * This is kept private as waiting should be done via wait_for_free_buffer(). + */ + util::timed_semaphore m_free_image_semaphore; +}; + +} /* namespace wsi */ |