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 <dennis.wildmark@arm.com>
This commit is contained in:
Dennis Wildmark 2024-03-06 09:39:05 +01:00
parent 46c770bea6
commit 23cd21c99c
9 changed files with 915 additions and 14 deletions

View file

@ -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)

View file

@ -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) \

View file

@ -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 <size_t array_size>
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<uint32_t, array_size> m_handle{ UINT32_MAX };
};
/* Forward declaration */
class drm_display;

72
wsi/display/surface.cpp Normal file
View file

@ -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<swapchain_base> 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<swapchain_base>(alloc.make_unique<swapchain>(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 */

59
wsi/display/surface.hpp Normal file
View file

@ -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<swapchain_base> 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 */

View file

@ -26,6 +26,7 @@
#include <algorithm>
#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<drm_display_mode *>(pCreateInfo->displayMode);
auto wsi_surface = allocator.make_unique<surface>(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<wsi::surface>(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);

View file

@ -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<drm_display> display;
surface *const m_specific_surface;
};
} /* namespace display */

545
wsi/display/swapchain.cpp Normal file
View file

@ -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 <vulkan/vk_icd.h>
#include <vulkan/vulkan.h>
#include <wsi/swapchain_base.hpp>
#include "swapchain.hpp"
#include "surface.hpp"
#include "util/macros.hpp"
#include <errno.h>
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<display_image_data *>(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<wsialloc_format> &importable_formats,
util::vector<uint64_t> &exportable_modifers)
{
/* Query supported modifers. */
util::vector<VkDrmFormatModifierPropertiesEXT> 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<wsialloc_format> &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<unsigned>(importable_formats.size()),
image_create_info.extent.width, image_create_info.extent.height,
allocation_flags };
std::array<int, MAX_PLANES> strides{};
std::array<int, MAX_PLANES> buffer_fds{ -1, -1, -1, -1 };
std::array<uint32_t, MAX_PLANES> 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<int>(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<VkSubresourceLayout> &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<wsialloc_format> 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<uint64_t> 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<uint32_t, util::MAX_PLANES> strides{ 0, 0, 0, 0 };
auto &display = drm_display::get_display();
if (!display.has_value())
{
return VK_ERROR_INITIALIZATION_FAILED;
}
drm_gem_handle_array<util::MAX_PLANES> 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<display_image_data>(1, m_device, m_allocator);
if (image_data == nullptr)
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
image.data = image_data;
image.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<display_image_data *>(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<display_image_data *>(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<display_image_data *>(image.data);
return data->present_fence.wait_payload(timeout);
}
void swapchain::destroy_image(swapchain_image &image)
{
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
if (image.status != swapchain_image::INVALID)
{
if (image.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<display_image_data *>(image.data);
auto &display = drm_display::get_display();
if (!display.has_value())
{
return;
}
if (image_data->fb_id != std::numeric_limits<uint32_t>::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*/

129
wsi/display/swapchain.hpp Normal file
View file

@ -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 <vulkan/vk_icd.h>
#include <vulkan/vulkan.h>
#include <wsi/swapchain_base.hpp>
#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<uint32_t>::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<VkSubresourceLayout> 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<wsialloc_format> &importable_formats, wsialloc_format *allocated_format);
VkResult get_surface_compatible_formats(const VkImageCreateInfo &info,
util::vector<wsialloc_format> &importable_formats,
util::vector<uint64_t> &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*/