From 23cd21c99c67585817f89e0d09f9a74c7d7f0edd Mon Sep 17 00:00:00 2001 From: Dennis Wildmark Date: Wed, 6 Mar 2024 09:39:05 +0100 Subject: [PATCH] VK_KHR_display swapchain implementation Implementation of VK_KHR_display swapchain class. VK_KHR_display is another WSI backend that provides surfaces to render directly to a display. The swapchain class is responsible for creating and managing a swapchain based on the specific type of surface provided by VK_KHR_display. Change-Id: I9b103c20c3444b1ca75f3df7edf6b66fc87a0992 Signed-off-by: Dennis Wildmark --- CMakeLists.txt | 17 +- layer/private_data.hpp | 2 + wsi/display/drm_display.hpp | 38 ++ wsi/display/surface.cpp | 72 ++++ wsi/display/surface.hpp | 59 ++++ wsi/display/surface_properties.cpp | 57 ++- wsi/display/surface_properties.hpp | 10 +- wsi/display/swapchain.cpp | 545 +++++++++++++++++++++++++++++ wsi/display/swapchain.hpp | 129 +++++++ 9 files changed, 915 insertions(+), 14 deletions(-) create mode 100644 wsi/display/surface.cpp create mode 100644 wsi/display/surface.hpp create mode 100644 wsi/display/swapchain.cpp create mode 100644 wsi/display/swapchain.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d80c2f..ebb14b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,17 +61,13 @@ option(BUILD_WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN "Build with support for VK_ set(SELECT_EXTERNAL_ALLOCATOR "none" CACHE STRING "Select an external system allocator (none, ion)") set(EXTERNAL_WSIALLOC_LIBRARY "" CACHE STRING "External implementation of the wsialloc interface to use") -if(BUILD_WSI_WAYLAND) +if(BUILD_WSI_WAYLAND OR BUILD_WSI_DISPLAY) set(BUILD_DRM_UTILS true) if(SELECT_EXTERNAL_ALLOCATOR STREQUAL "none") - message(FATAL_ERROR "Wayland only supported with an external allocator.") + message(FATAL_ERROR "WSI only supported with an external allocator.") endif() endif() -if(BUILD_WSI_DISPLAY) - set(BUILD_DRM_UTILS true) -endif() - if(BUILD_DRM_UTILS) add_library(drm_utils STATIC util/drm/drm_utils.cpp) @@ -191,7 +187,9 @@ endif() if (BUILD_WSI_DISPLAY) add_library(wsi_display STATIC wsi/display/drm_display.cpp - wsi/display/surface_properties.cpp) + wsi/display/surface_properties.cpp + wsi/display/swapchain.cpp + wsi/display/surface.cpp) pkg_check_modules(LIBDRM REQUIRED libdrm) message(STATUS "Using libdrm include directories: ${LIBDRM_INCLUDE_DIRS}") @@ -208,6 +206,11 @@ if (BUILD_WSI_DISPLAY) target_compile_options(wsi_display INTERFACE "-DBUILD_WSI_DISPLAY=1") target_link_libraries(wsi_display ${LIBDRM_LDFLAGS} drm) target_link_libraries(wsi_display drm_utils) + if(NOT EXTERNAL_WSIALLOC_LIBRARY STREQUAL "") + target_link_libraries(wsi_display ${EXTERNAL_WSIALLOC_LIBRARY}) + else() + target_link_libraries(wsi_display wsialloc) + endif() list(APPEND LINK_WSI_LIBS wsi_display) else() list(APPEND JSON_COMMANDS COMMAND sed -i '/VK_KHR_display/d' ${CMAKE_CURRENT_BINARY_DIR}/VkLayer_window_system_integration.json) diff --git a/layer/private_data.hpp b/layer/private_data.hpp index 5ec055a..37f13a3 100644 --- a/layer/private_data.hpp +++ b/layer/private_data.hpp @@ -222,6 +222,8 @@ static constexpr uint32_t API_VERSION_MAX = UINT32_MAX; EP(CreateHeadlessSurfaceEXT, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, API_VERSION_MAX, false) \ /* VK_KHR_wayland_surface */ \ EP(CreateWaylandSurfaceKHR, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, API_VERSION_MAX, false) \ + /* VK_KHR_display */ \ + EP(CreateDisplayPlaneSurfaceKHR, VK_KHR_DISPLAY_EXTENSION_NAME, API_VERSION_MAX, false) \ /* VK_KHR_get_surface_capabilities2 */ \ EP(GetPhysicalDeviceSurfaceCapabilities2KHR, VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, API_VERSION_MAX, \ false) \ diff --git a/wsi/display/drm_display.hpp b/wsi/display/drm_display.hpp index ffae428..8d1185a 100644 --- a/wsi/display/drm_display.hpp +++ b/wsi/display/drm_display.hpp @@ -61,6 +61,44 @@ using drm_plane_resources_owner = drm_owner<_drmModePlaneRes, drmModeFreePlaneRe using drm_object_properties_owner = drm_owner<_drmModeObjectProperties, drmModeFreeObjectProperties>; using drm_property_owner = drm_owner<_drmModeProperty, drmModeFreeProperty>; +/** + * @brief Owner class for an array of DRM GEM buffer handles. + */ +template +class drm_gem_handle_array : private util::noncopyable +{ +public: + drm_gem_handle_array(int fd) + : m_fd(fd) + { + } + + uint32_t &operator[](size_t size) + { + return m_handle[size]; + } + + uint32_t *data() + { + return m_handle.data(); + } + + ~drm_gem_handle_array() + { + for (auto handle : m_handle) + { + if (handle != UINT32_MAX && m_fd != -1) + { + drmCloseBufferHandle(m_fd, handle); + } + } + } + +private: + int m_fd{ -1 }; + std::array m_handle{ UINT32_MAX }; +}; + /* Forward declaration */ class drm_display; diff --git a/wsi/display/surface.cpp b/wsi/display/surface.cpp new file mode 100644 index 0000000..651a427 --- /dev/null +++ b/wsi/display/surface.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 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 + * @brief Implementation of a headless WSI Surface + */ + +#include "surface.hpp" +#include "swapchain.hpp" +#include "surface_properties.hpp" + +namespace wsi +{ +namespace display +{ + +surface::surface(drm_display_mode *display_mode) + : m_display_mode(display_mode) + , m_surface_properties(*this) +{ +} + +wsi::surface_properties &surface::get_properties() +{ + return m_surface_properties; +} + +util::unique_ptr surface::allocate_swapchain(layer::device_private_data &dev_data, + const VkAllocationCallbacks *allocator) +{ + util::allocator alloc{ dev_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, allocator }; + return util::unique_ptr(alloc.make_unique(dev_data, allocator, *this)); +} + +VkExtent2D surface::get_current_extent() const +{ + return m_current_extent; +} + +void surface::set_current_extent(VkExtent2D extent) +{ + m_current_extent = extent; +} + +drm_display_mode *surface::get_display_mode() +{ + return m_display_mode; +} + +} /* namespace display */ +} /* namespace wsi */ diff --git a/wsi/display/surface.hpp b/wsi/display/surface.hpp new file mode 100644 index 0000000..36e9755 --- /dev/null +++ b/wsi/display/surface.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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 + * @brief Definitions for a headless WSI Surface + */ + +#pragma once + +#include "wsi/surface.hpp" +#include "surface_properties.hpp" +#include "drm_display.hpp" + +namespace wsi +{ +namespace display +{ + +class surface : public wsi::surface +{ +public: + surface(drm_display_mode *mode); + wsi::surface_properties &get_properties() override; + util::unique_ptr allocate_swapchain(layer::device_private_data &dev_data, + const VkAllocationCallbacks *allocator) override; + + VkExtent2D get_current_extent() const; + void set_current_extent(VkExtent2D extent); + drm_display_mode *get_display_mode(); + +private: + drm_display_mode *m_display_mode; + VkExtent2D m_current_extent; + surface_properties m_surface_properties; +}; + +} /* namespace display */ +} /* namespace wsi */ diff --git a/wsi/display/surface_properties.cpp b/wsi/display/surface_properties.cpp index ea03c1c..62d4997 100644 --- a/wsi/display/surface_properties.cpp +++ b/wsi/display/surface_properties.cpp @@ -26,6 +26,7 @@ #include #include "surface_properties.hpp" +#include "surface.hpp" #include "util/macros.hpp" namespace wsi @@ -36,11 +37,28 @@ namespace display constexpr int max_core_1_0_formats = VK_FORMAT_ASTC_12x12_SRGB_BLOCK + 1; +surface_properties::surface_properties() + : m_specific_surface(nullptr) +{ +} + +surface_properties::surface_properties(surface &wsi_surface) + : m_specific_surface(&wsi_surface) +{ +} + VkResult surface_properties::get_surface_capabilities(VkPhysicalDevice physical_device, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) { get_surface_capabilities_common(physical_device, pSurfaceCapabilities); + if (m_specific_surface != nullptr) + { + pSurfaceCapabilities->currentExtent = m_specific_surface->get_current_extent(); + pSurfaceCapabilities->minImageExtent = m_specific_surface->get_current_extent(); + pSurfaceCapabilities->maxImageExtent = m_specific_surface->get_current_extent(); + } + /* Image count limits */ pSurfaceCapabilities->minImageCount = 2; pSurfaceCapabilities->maxImageCount = 3; @@ -240,13 +258,30 @@ VWL_VKAPI_CALL(VkResult) CreateDisplayPlaneSurfaceKHR(VkInstance instance, const VkDisplaySurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { - UNUSED(instance); - UNUSED(pCreateInfo); - UNUSED(pAllocator); - UNUSED(pSurface); - // TODO: Create the surface object here, which in turn creates the surface_properties object + auto &instance_data = layer::instance_private_data::get(instance); + util::allocator allocator{ instance_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT, pAllocator }; - return VK_ERROR_EXTENSION_NOT_PRESENT; + drm_display_mode *display_mode = reinterpret_cast(pCreateInfo->displayMode); + + auto wsi_surface = allocator.make_unique(display_mode); + if (wsi_surface == nullptr) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + VkResult res = instance_data.disp.CreateDisplayPlaneSurfaceKHR(instance, pCreateInfo, pAllocator, pSurface); + if (res == VK_SUCCESS) + { + + wsi_surface->set_current_extent(pCreateInfo->imageExtent); + auto surface_base = util::unique_ptr(std::move(wsi_surface)); + res = instance_data.add_surface(*pSurface, surface_base); + if (res != VK_SUCCESS) + { + instance_data.disp.DestroySurfaceKHR(instance, *pSurface, pAllocator); + } + } + return res; } VWL_VKAPI_CALL(VkResult) @@ -504,6 +539,16 @@ VkResult surface_properties::get_required_instance_extensions(util::extension_li return extension_list.add(required_instance_extensions.data(), required_instance_extensions.size()); } +VkResult surface_properties::get_required_device_extensions(util::extension_list &extension_list) +{ + const std::array required_device_extensions{ + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, + }; + + return extension_list.add(required_device_extensions.data(), required_device_extensions.size()); +} + bool surface_properties::is_surface_extension_enabled(const layer::instance_private_data &instance_data) { return instance_data.is_instance_extension_enabled(VK_KHR_SURFACE_EXTENSION_NAME); diff --git a/wsi/display/surface_properties.hpp b/wsi/display/surface_properties.hpp index 8f3526e..317f761 100644 --- a/wsi/display/surface_properties.hpp +++ b/wsi/display/surface_properties.hpp @@ -35,9 +35,15 @@ namespace wsi namespace display { +class surface; + class surface_properties : public wsi::surface_properties { public: + surface_properties(); + + surface_properties(surface &wsi_surface); + VkResult get_surface_capabilities(VkPhysicalDevice physical_device, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) override; @@ -52,12 +58,14 @@ public: VkResult get_required_instance_extensions(util::extension_list &extension_list) override; + VkResult get_required_device_extensions(util::extension_list &extension_list) override; + bool is_surface_extension_enabled(const layer::instance_private_data &instance_data) override; static surface_properties &get_instance(); private: - std::shared_ptr display; + surface *const m_specific_surface; }; } /* namespace display */ diff --git a/wsi/display/swapchain.cpp b/wsi/display/swapchain.cpp new file mode 100644 index 0000000..cbf3c8b --- /dev/null +++ b/wsi/display/swapchain.cpp @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2024 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 class implementation for a display swapchain. + */ + +#include +#include +#include +#include "swapchain.hpp" +#include "surface.hpp" +#include "util/macros.hpp" + +#include +namespace wsi +{ + +namespace display +{ + +swapchain::swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator, + surface &wsi_surface) + : wsi::swapchain_base(dev_data, pAllocator) + , m_wsi_allocator(nullptr) + , m_display_mode(wsi_surface.get_display_mode()) + , m_image_creation_parameters({}, m_allocator, {}, {}) +{ + m_image_create_info.format = VK_FORMAT_UNDEFINED; +} + +swapchain::~swapchain() +{ + /* Call the base class teardown */ + teardown(); + + /* Free WSI allocator. */ + if (m_wsi_allocator != nullptr) + { + wsialloc_delete(m_wsi_allocator); + } + m_wsi_allocator = nullptr; +} + +static void page_flip_event(int fd, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, void *user_data) +{ + UNUSED(fd); + UNUSED(sequence); + UNUSED(tv_sec); + UNUSED(tv_usec); + bool *done = (bool *)user_data; + *done = true; +} + +VkResult swapchain::init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info, + bool &use_presentation_thread) +{ + + WSIALLOC_ASSERT_VERSION(); + if (wsialloc_new(&m_wsi_allocator) != WSIALLOC_ERROR_NONE) + { + WSI_LOG_ERROR("Failed to create wsi allocator."); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return VK_SUCCESS; +} + +VkResult swapchain::bind_swapchain_image(VkDevice &device, const VkBindImageMemoryInfo *bind_image_mem_info, + const VkBindImageMemorySwapchainInfoKHR *bind_sc_info) +{ + const wsi::swapchain_image &swapchain_image = m_swapchain_images[bind_sc_info->imageIndex]; + auto image_data = reinterpret_cast(swapchain_image.data); + return image_data->external_mem.bind_swapchain_image_memory(bind_image_mem_info->image); +} + +VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info, + util::vector &importable_formats, + util::vector &exportable_modifers) +{ + /* Query supported modifers. */ + util::vector drm_format_props( + util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); + + TRY_LOG(util::get_drm_format_properties(m_device_data.physical_device, info.format, drm_format_props), + "Failed to get format properties"); + + for (const auto &prop : drm_format_props) + { + drm_format_pair drm_format{ util::drm::vk_to_drm_format(info.format), prop.drmFormatModifier }; + + // TODO query surface class for support of format+modifier combination. For now support no modifiers. + if ((drm_format.modifier != 0)) + { + continue; + } + + VkExternalImageFormatPropertiesKHR external_props = {}; + external_props.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES_KHR; + + VkImageFormatProperties2KHR format_props = {}; + format_props.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2_KHR; + format_props.pNext = &external_props; + + VkResult result = VK_SUCCESS; + { + VkPhysicalDeviceExternalImageFormatInfoKHR external_info = {}; + external_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO_KHR; + external_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; + + VkPhysicalDeviceImageDrmFormatModifierInfoEXT drm_mod_info = {}; + drm_mod_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT; + drm_mod_info.pNext = &external_info; + drm_mod_info.drmFormatModifier = prop.drmFormatModifier; + drm_mod_info.sharingMode = info.sharingMode; + drm_mod_info.queueFamilyIndexCount = info.queueFamilyIndexCount; + drm_mod_info.pQueueFamilyIndices = info.pQueueFamilyIndices; + + VkPhysicalDeviceImageFormatInfo2KHR image_info = {}; + image_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR; + image_info.pNext = &drm_mod_info; + image_info.format = info.format; + image_info.type = info.imageType; + image_info.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + image_info.usage = info.usage; + image_info.flags = info.flags; + +#if WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN + VkImageCompressionControlEXT compression_control = {}; + compression_control.sType = VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT; + compression_control.flags = m_image_compression_control_params.flags; + compression_control.compressionControlPlaneCount = + m_image_compression_control_params.compression_control_plane_count; + compression_control.pFixedRateFlags = m_image_compression_control_params.fixed_rate_flags.data(); + + if (m_device_data.is_swapchain_compression_control_enabled()) + { + compression_control.pNext = image_info.pNext; + image_info.pNext = &compression_control; + } +#endif + result = m_device_data.instance_data.disp.GetPhysicalDeviceImageFormatProperties2KHR( + m_device_data.physical_device, &image_info, &format_props); + } + if (result != VK_SUCCESS) + { + continue; + } + if (format_props.imageFormatProperties.maxExtent.width < info.extent.width || + format_props.imageFormatProperties.maxExtent.height < info.extent.height || + format_props.imageFormatProperties.maxExtent.depth < info.extent.depth) + { + continue; + } + if (format_props.imageFormatProperties.maxMipLevels < info.mipLevels || + format_props.imageFormatProperties.maxArrayLayers < info.arrayLayers) + { + continue; + } + if ((format_props.imageFormatProperties.sampleCounts & info.samples) != info.samples) + { + continue; + } + + if (external_props.externalMemoryProperties.externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_KHR) + { + if (!exportable_modifers.try_push_back(drm_format.modifier)) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + } + + if (external_props.externalMemoryProperties.externalMemoryFeatures & + VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) + { + uint64_t flags = + (prop.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT) ? 0 : WSIALLOC_FORMAT_NON_DISJOINT; + wsialloc_format import_format{ drm_format.fourcc, drm_format.modifier, flags }; + if (!importable_formats.try_push_back(import_format)) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + } + } + + return VK_SUCCESS; +} + +VkResult swapchain::allocate_wsialloc(VkImageCreateInfo &image_create_info, display_image_data *image_data, + util::vector &importable_formats, + wsialloc_format *allocated_format) +{ + bool is_protected_memory = (image_create_info.flags & VK_IMAGE_CREATE_PROTECTED_BIT) != 0; + uint64_t allocation_flags = is_protected_memory ? WSIALLOC_ALLOCATE_PROTECTED : 0; + +#if WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN + if (m_image_compression_control_params.flags & VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT) + { + allocation_flags |= WSIALLOC_ALLOCATE_HIGHEST_FIXED_RATE_COMPRESSION; + } +#endif + + wsialloc_allocate_info alloc_info = { importable_formats.data(), static_cast(importable_formats.size()), + image_create_info.extent.width, image_create_info.extent.height, + allocation_flags }; + + std::array strides{}; + std::array buffer_fds{ -1, -1, -1, -1 }; + std::array offsets{}; + const auto res = + wsialloc_alloc(m_wsi_allocator, &alloc_info, allocated_format, strides.data(), buffer_fds.data(), offsets.data()); + if (res != WSIALLOC_ERROR_NONE) + { + WSI_LOG_ERROR("Failed allocation of DMA Buffer. WSI error: %d", static_cast(res)); + if (res == WSIALLOC_ERROR_NOT_SUPPORTED) + { + return VK_ERROR_FORMAT_NOT_SUPPORTED; + } + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + auto &external_memory = image_data->external_mem; + external_memory.set_strides(strides); + external_memory.set_buffer_fds(buffer_fds); + external_memory.set_offsets(offsets); + external_memory.set_memory_handle_type(VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT); + return VK_SUCCESS; +} + +static VkResult fill_image_create_info(VkImageCreateInfo &image_create_info, + util::vector &image_plane_layouts, + VkImageDrmFormatModifierExplicitCreateInfoEXT &drm_mod_info, + VkExternalMemoryImageCreateInfoKHR &external_info, + display_image_data &image_data, uint64_t modifier) +{ + TRY_LOG_CALL(image_data.external_mem.fill_image_plane_layouts(image_plane_layouts)); + + if (image_data.external_mem.is_disjoint()) + { + image_create_info.flags |= VK_IMAGE_CREATE_DISJOINT_BIT; + } + + image_data.external_mem.fill_drm_mod_info(image_create_info.pNext, drm_mod_info, image_plane_layouts, modifier); + image_data.external_mem.fill_external_info(external_info, &drm_mod_info); + image_create_info.pNext = &external_info; + image_create_info.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT; + return VK_SUCCESS; +} + +VkResult swapchain::allocate_image(VkImageCreateInfo &image_create_info, display_image_data *image_data, VkImage *image) +{ + util::vector importable_formats(util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); + auto &m_allocated_format = m_image_creation_parameters.m_allocated_format; + if (m_image_create_info.format != VK_FORMAT_UNDEFINED) + { + if (!importable_formats.try_push_back(m_allocated_format)) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + TRY_LOG_CALL(allocate_wsialloc(m_image_create_info, image_data, importable_formats, &m_allocated_format)); + } + else + { + util::vector exportable_modifiers(util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); + TRY_LOG_CALL(get_surface_compatible_formats(image_create_info, importable_formats, exportable_modifiers)); + + /* TODO: Handle exportable images which use ICD allocated memory in preference to an external allocator. */ + if (importable_formats.empty()) + { + WSI_LOG_ERROR("Export/Import not supported."); + return VK_ERROR_INITIALIZATION_FAILED; + } + + wsialloc_format allocated_format = { 0 }; + TRY_LOG_CALL(allocate_wsialloc(image_create_info, image_data, importable_formats, &allocated_format)); + + TRY_LOG_CALL(fill_image_create_info( + image_create_info, m_image_creation_parameters.m_image_layout, m_image_creation_parameters.m_drm_mod_info, + m_image_creation_parameters.m_external_info, *image_data, allocated_format.modifier)); + + m_image_create_info = image_create_info; + m_allocated_format = allocated_format; + } + + TRY_LOG(m_device_data.disp.CreateImage(m_device, &m_image_create_info, get_allocation_callbacks(), image), + "Image creation failed"); + return VK_SUCCESS; +} + +VkResult swapchain::create_framebuffer(const VkImageCreateInfo &image_create_info, swapchain_image &image, + display_image_data *image_data) +{ + VkResult ret_code = VK_SUCCESS; + std::array strides{ 0, 0, 0, 0 }; + + auto &display = drm_display::get_display(); + if (!display.has_value()) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + drm_gem_handle_array buffer_handles{ display->get_drm_fd() }; + + const auto &buffer_fds = image_data->external_mem.get_buffer_fds(); + + for (uint32_t plane = 0; plane < image_data->external_mem.get_num_planes(); plane++) + { + assert(image_data->external_mem.get_strides()[plane] > 0); + strides[plane] = image_data->external_mem.get_strides()[plane]; + if (drmPrimeFDToHandle(display->get_drm_fd(), buffer_fds[plane], &buffer_handles[plane]) != 0) + { + WSI_LOG_ERROR("Failed to convert buffer FD to GEM handle: %s", std::strerror(errno)); + return VK_ERROR_INITIALIZATION_FAILED; + } + } + + int error = drmModeAddFB2(display->get_drm_fd(), image_create_info.extent.width, image_create_info.extent.height, + m_image_creation_parameters.m_allocated_format.fourcc, buffer_handles.data(), + strides.data(), image_data->external_mem.get_offsets().data(), &image_data->fb_id, 0); + + if (error != 0) + { + WSI_LOG_ERROR("Failed to create framebuffer: %s", strerror(errno)); + return VK_ERROR_INITIALIZATION_FAILED; + } + + return ret_code; +} + +VkResult swapchain::create_and_bind_swapchain_image(VkImageCreateInfo image_create_info, swapchain_image &image) +{ + /* Create image_data */ + auto image_data = m_allocator.create(1, m_device, m_allocator); + if (image_data == nullptr) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + std::unique_lock image_status_lock(m_image_status_mutex); + image.data = image_data; + image.status = swapchain_image::FREE; + + TRY_LOG(allocate_image(image_create_info, image_data, &image.image), "Failed to allocate image"); + image_status_lock.unlock(); + + TRY_LOG(create_framebuffer(image_create_info, image, image_data), "Failed to create framebuffer"); + + TRY_LOG(image_data->external_mem.import_memory_and_bind_swapchain_image(image.image), + "Failed to import memory and bind swapchain image"); + + /* Initialize presentation fence. */ + auto present_fence = sync_fd_fence_sync::create(m_device_data); + if (!present_fence.has_value()) + { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + image_data->present_fence = std::move(present_fence.value()); + + return VK_SUCCESS; +} + +void swapchain::present_image(uint32_t pending_index) +{ + int drm_res = 0; + display_image_data *image_data = reinterpret_cast(m_swapchain_images[pending_index].data); + const auto &display = drm_display::get_display(); + if (!display.has_value()) + { + set_error_state(VK_ERROR_SURFACE_LOST_KHR); + return; + } + + if (m_first_present) + { + /* Now we can set the mode of the new swapchain. */ + drmModeModeInfo modeInfo = m_display_mode->get_drm_mode(); + + uint32_t connector_id = display->get_connector_id(); + drm_res = drmModeSetCrtc(display->get_drm_fd(), display->get_crtc_id(), image_data->fb_id, 0, 0, &connector_id, 1, + &modeInfo); + + if (drm_res != 0) + { + WSI_LOG_ERROR("drmModeSetCrtc failed: %s\n", std::strerror(errno)); + set_error_state(VK_ERROR_SURFACE_LOST_KHR); + return; + } + } + /* The swapchain has already started presenting. */ + else + { + + bool page_flip_complete = false; + + drm_res = drmModePageFlip(display->get_drm_fd(), display->get_crtc_id(), image_data->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, (void *)&page_flip_complete); + + if (drm_res != 0) + { + WSI_LOG_ERROR("drmModePageFlip failed: %s\n", std::strerror(errno)); + set_error_state(VK_ERROR_SURFACE_LOST_KHR); + return; + } + + fd_set fds; + FD_ZERO(&fds); + FD_SET(display->get_drm_fd(), &fds); + + do + { + struct timeval t; + t.tv_sec = 1; + t.tv_usec = 0; + drm_res = select(display->get_drm_fd() + 1, &fds, NULL, NULL, &t); + + if (drm_res < 0) + { + if (errno != EINTR && errno != EAGAIN) + { + WSI_LOG_ERROR("select() failed with errno: %d\n", errno); + set_error_state(VK_ERROR_SURFACE_LOST_KHR); + break; + } + WSI_LOG_ERROR("select() failed with %d, carrying on with page flip\n", errno); + } + else if (drm_res == 0) + { + WSI_LOG_ERROR("select() timed out, carrying on with page flip\n"); + } + else + { + assert(FD_ISSET(display->get_drm_fd(), &fds)); + + drmEventContext ev = {}; + ev.version = DRM_EVENT_CONTEXT_VERSION; + ev.page_flip_handler = page_flip_event; + + drmHandleEvent(display->get_drm_fd(), &ev); + } + } while ((drm_res == -1 && (errno == EINTR || errno == EAGAIN)) || drm_res == 0 || !page_flip_complete); + } + + /* Find currently presented image */ + uint32_t presented_index = m_swapchain_images.size(); + if (!m_first_present) + { + for (uint32_t i = 0; i < m_swapchain_images.size(); ++i) + { + if (m_swapchain_images[i].status == swapchain_image::PRESENTED) + { + presented_index = i; + break; + } + } + /* There should always be a presented image, unless there was an error */ + assert(presented_index < m_swapchain_images.size()); + } + /* The image is on screen, change the image status to PRESENTED. */ + m_swapchain_images[pending_index].status = swapchain_image::PRESENTED; + /* And release the old one. */ + if (presented_index < m_swapchain_images.size()) + { + unpresent_image(presented_index); + } + + return; +} + +VkResult swapchain::image_set_present_payload(swapchain_image &image, VkQueue queue, const VkSemaphore *sem_payload, + uint32_t sem_count) +{ + + auto image_data = reinterpret_cast(image.data); + return image_data->present_fence.set_payload(queue, sem_payload, sem_count); +} + +VkResult swapchain::image_wait_present(swapchain_image &image, uint64_t timeout) +{ + auto data = reinterpret_cast(image.data); + return data->present_fence.wait_payload(timeout); +} + +void swapchain::destroy_image(swapchain_image &image) +{ + std::unique_lock image_status_lock(m_image_status_mutex); + + if (image.status != swapchain_image::INVALID) + { + if (image.image != VK_NULL_HANDLE) + { + m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks()); + image.image = VK_NULL_HANDLE; + } + + image.status = swapchain_image::INVALID; + } + + image_status_lock.unlock(); + + if (image.data != nullptr) + { + auto image_data = reinterpret_cast(image.data); + auto &display = drm_display::get_display(); + if (!display.has_value()) + { + return; + } + if (image_data->fb_id != std::numeric_limits::max()) + { + + int result = drmModeRmFB(display->get_drm_fd(), image_data->fb_id); + assert(result == 0); + } + + m_allocator.destroy(1, image_data); + image.data = nullptr; + } +} + +} /* namespace display */ + +} /* namespace wsi*/ diff --git a/wsi/display/swapchain.hpp b/wsi/display/swapchain.hpp new file mode 100644 index 0000000..d866bb0 --- /dev/null +++ b/wsi/display/swapchain.hpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 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 display swapchain. + */ + +#pragma once + +#include +#include +#include +#include "util/wsialloc/wsialloc.h" +#include "drm_display.hpp" +#include "wsi/external_memory.hpp" +#include "surface.hpp" + +namespace wsi +{ + +namespace display +{ + +struct display_image_data +{ + display_image_data(const VkDevice &device, const util::allocator &allocator) + : external_mem(device, allocator) + , fb_id(std::numeric_limits::max()) + { + } + + external_memory external_mem; + uint32_t fb_id; + sync_fd_fence_sync present_fence; +}; + +struct image_creation_parameters +{ + wsialloc_format m_allocated_format; + util::vector m_image_layout; + VkExternalMemoryImageCreateInfoKHR m_external_info; + VkImageDrmFormatModifierExplicitCreateInfoEXT m_drm_mod_info; + + image_creation_parameters(wsialloc_format allocated_format, util::allocator allocator, + VkExternalMemoryImageCreateInfoKHR external_info, + VkImageDrmFormatModifierExplicitCreateInfoEXT drm_mod_info) + : m_allocated_format(allocated_format) + , m_image_layout(allocator) + , m_external_info(external_info) + , m_drm_mod_info(drm_mod_info) + { + } +}; + +struct drm_format_pair +{ + uint32_t fourcc; + uint64_t modifier; +}; + +/** + * @brief Display swapchain class. + */ +class swapchain : public wsi::swapchain_base +{ +public: + swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator, surface &wsi_surface); + virtual ~swapchain(); + virtual VkResult init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info, + bool &use_presentation_thread) override; + + virtual VkResult bind_swapchain_image(VkDevice &device, const VkBindImageMemoryInfo *bind_image_mem_info, + const VkBindImageMemorySwapchainInfoKHR *bind_sc_info) override; + + virtual VkResult create_and_bind_swapchain_image(VkImageCreateInfo image_create_info, + swapchain_image &image) override; + + virtual void present_image(uint32_t pending_index) override; + + virtual VkResult image_set_present_payload(swapchain_image &image, VkQueue queue, const VkSemaphore *sem_payload, + uint32_t sem_count) override; + virtual VkResult image_wait_present(swapchain_image &image, uint64_t timeout) override; + + void destroy_image(swapchain_image &image) override; + +private: + VkResult allocate_image(VkImageCreateInfo &image_create_info, display_image_data *image_data, VkImage *image); + + VkResult allocate_wsialloc(VkImageCreateInfo &image_create_info, display_image_data *image_data, + util::vector &importable_formats, wsialloc_format *allocated_format); + + VkResult get_surface_compatible_formats(const VkImageCreateInfo &info, + util::vector &importable_formats, + util::vector &exportable_modifers); + + VkResult create_framebuffer(const VkImageCreateInfo &image_create_info, swapchain_image &image, + display_image_data *image_data); + + wsialloc_allocator *m_wsi_allocator; + drm_display_mode *m_display_mode; + image_creation_parameters m_image_creation_parameters; +}; + +} /* namespace display */ + +} /* namespace wsi*/