From 19cd4376d1b973fbfadec5b291d40b94e368953b Mon Sep 17 00:00:00 2001 From: Dennis Wildmark Date: Wed, 31 Jan 2024 12:48:32 +0100 Subject: [PATCH] VK_KHR_display surface_properties implementation Implementation of the surface_properties interface for VK_KHR_display. Signed-off-by: Dennis Wildmark Change-Id: Idd3b53bec0d0a829478d51b4263db767c872d0e5 --- CMakeLists.txt | 33 +- layer/VkLayer_window_system_integration.json | 1 + util/file_descriptor.hpp | 11 +- util/macros.hpp | 7 +- wsi/display/drm_display.cpp | 300 +++++++++++ wsi/display/drm_display.hpp | 296 +++++++++++ wsi/display/surface_properties.cpp | 509 +++++++++++++++++++ wsi/display/surface_properties.hpp | 67 +++ wsi/headless/surface_properties.cpp | 4 +- wsi/wsi_factory.cpp | 13 +- 10 files changed, 1228 insertions(+), 13 deletions(-) create mode 100644 wsi/display/drm_display.cpp create mode 100644 wsi/display/drm_display.hpp create mode 100644 wsi/display/surface_properties.cpp create mode 100644 wsi/display/surface_properties.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b06897c..3d80c2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2022 Arm Limited. +# Copyright (c) 2019-2024 Arm Limited. # # SPDX-License-Identifier: MIT # @@ -56,6 +56,7 @@ endif() # Build Configuration options option(BUILD_WSI_HEADLESS "Build with support for VK_EXT_headless_surface" ON) option(BUILD_WSI_WAYLAND "Build with support for VK_KHR_wayland_surface" OFF) +option(BUILD_WSI_DISPLAY "Build with support for VK_KHR_display" OFF) option(BUILD_WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN "Build with support for VK_EXT_image_compression_control_swapchain" OFF) 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") @@ -67,6 +68,10 @@ if(BUILD_WSI_WAYLAND) 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) @@ -182,6 +187,32 @@ else() list(APPEND JSON_COMMANDS COMMAND sed -i '/VK_EXT_headless_surface/d' ${CMAKE_CURRENT_BINARY_DIR}/VkLayer_window_system_integration.json) endif() +# Display +if (BUILD_WSI_DISPLAY) + add_library(wsi_display STATIC + wsi/display/drm_display.cpp + wsi/display/surface_properties.cpp) + + pkg_check_modules(LIBDRM REQUIRED libdrm) + message(STATUS "Using libdrm include directories: ${LIBDRM_INCLUDE_DIRS}") + message(STATUS "Using libdrm ldflags: ${LIBDRM_LDFLAGS}") + + target_include_directories(wsi_display PRIVATE + ${PROJECT_SOURCE_DIR} + ${VULKAN_CXX_INCLUDE} + ${CMAKE_CURRENT_BINARY_DIR}) + + target_include_directories(wsi_display PUBLIC + ${LIBDRM_INCLUDE_DIRS}) + + 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) + 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) +endif() + # Layer add_library(${PROJECT_NAME} SHARED layer/layer.cpp diff --git a/layer/VkLayer_window_system_integration.json b/layer/VkLayer_window_system_integration.json index f5c922c..d0f43c6 100644 --- a/layer/VkLayer_window_system_integration.json +++ b/layer/VkLayer_window_system_integration.json @@ -14,6 +14,7 @@ {"name" : "VK_EXT_headless_surface", "spec_version" : "1"}, {"name" : "VK_KHR_wayland_surface", "spec_version" : "6"}, {"name" : "VK_KHR_surface", "spec_version" : "25"}, + {"name" : "VK_KHR_display", "spec_version" : "23"}, {"name" : "VK_KHR_get_surface_capabilities2", "spec_version" : "1"} ], "device_extensions": [ diff --git a/util/file_descriptor.hpp b/util/file_descriptor.hpp index 664848f..13a2633 100644 --- a/util/file_descriptor.hpp +++ b/util/file_descriptor.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Arm Limited. + * Copyright (c) 2021, 2024 Arm Limited. * * SPDX-License-Identifier: MIT * @@ -44,10 +44,9 @@ namespace util class fd_owner : private noncopyable { public: - fd_owner() = default; fd_owner(int fd) - : fd_handle{ fd } + : fd_handle{ fd } { } @@ -70,14 +69,14 @@ public: } } - int get() + int get() const { return fd_handle; } - bool is_valid() + bool is_valid() const { - return fd_handle >= 0; + return fd_handle >= 0; } private: diff --git a/util/macros.hpp b/util/macros.hpp index 505df90..e539bdb 100644 --- a/util/macros.hpp +++ b/util/macros.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023 Arm Limited. + * Copyright (c) 2021, 2023-2024 Arm Limited. * * SPDX-License-Identifier: MIT * @@ -51,4 +51,7 @@ #define VWL_VKAPI_EXPORT __attribute__((visibility("default"))) #else #define VWL_VKAPI_EXPORT -#endif \ No newline at end of file +#endif + +/* Unused parameter macro */ +#define UNUSED(x) ((void)(x)) \ No newline at end of file diff --git a/wsi/display/drm_display.cpp b/wsi/display/drm_display.cpp new file mode 100644 index 0000000..a35ab55 --- /dev/null +++ b/wsi/display/drm_display.cpp @@ -0,0 +1,300 @@ +/* + * 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. + */ + +#include "drm_display.hpp" +#include "util/custom_allocator.hpp" + +#include +#include +#include +#include +#include +namespace wsi +{ + +namespace display +{ + +drm_display::drm_display(util::fd_owner drm_fd, int crtc_id, drm_connector_owner drm_connector, + util::unique_ptr display_modes, size_t num_display_modes, uint32_t max_width, + uint32_t max_height) + : m_drm_fd(std::move(drm_fd)) + , m_crtc_id(crtc_id) + , m_drm_connector(std::move(drm_connector)) + , m_display_modes(std::move(display_modes)) + , m_num_display_modes(num_display_modes) + , m_max_width(max_width) + , m_max_height(max_height) +{ + for (size_t i = 0; i < num_display_modes; i++) + { + m_display_modes.get()[i].set_display(this); + } +} + +drm_display::~drm_display() +{ + if (m_drm_fd.is_valid()) + { + /* Finish using the DRM device. */ + drmDropMaster(m_drm_fd.get()); + } +} + +/** + * @brief Utility function to find a compatible CRTC to drive this display's connector. + * + * @return An integer < 0 on failure, otherwise a valid CRTC id. + */ +static int find_compatible_crtc(int fd, drm_resources_owner &resources, drm_connector_owner &connector) +{ + assert(resources); + assert(connector); + + for (int i = 0; i < connector->count_encoders; i++) + { + drm_encoder_owner encoder{ drmModeGetEncoder(fd, connector->encoders[i]) }; + if (!encoder) + { + /* Cannot find an encoder, ignore this one. */ + continue; + } + + /* Iterate over all global CRTCs. */ + for (int j = 0; j < resources->count_crtcs; j++) + { + /* Is this encoder compatible with the CRTC? */ + if (!(encoder->possible_crtcs & (1 << j))) + { + /* Encoder not compatible, so skip this CRTC. */ + continue; + } + + /* Make the assumption that only one connector will be in use at a time so there is no + * need to check that any other connectors are being driven by this CRTC. */ + return resources->crtcs[j]; + } + } + + WSI_LOG_WARNING("Failed to find compatible CRTC."); + + return -ENODEV; +} + +std::optional> drm_display::make_display(const util::allocator &allocator, + const char *drm_device) +{ + util::fd_owner drm_fd{ open(drm_device, O_RDWR | O_CLOEXEC, 0) }; + + if (!drm_fd.is_valid()) + { + WSI_LOG_ERROR("Failed to open DRM device %s.", drm_device); + return std::nullopt; + } + + /* Get the DRM master permission so that mode can be set on the drm device later. */ + if (!drmIsMaster(drm_fd.get())) + { + if (drmSetMaster(drm_fd.get()) != 0) + { + WSI_LOG_ERROR("Failed to set DRM master."); + return std::nullopt; + } + } + + drm_resources_owner resources{ drmModeGetResources(drm_fd.get()) }; + if (resources == nullptr) + { + WSI_LOG_ERROR("Failed to get DRM resources."); + return std::nullopt; + } + + drm_connector_owner connector{ nullptr }; + int crtc_id = -1; + for (int i = 0; i < resources->count_connectors; ++i) + { + drm_connector_owner temp_connector{ drmModeGetConnector(drm_fd.get(), resources->connectors[i]) }; + if (temp_connector != nullptr && temp_connector->connection == DRM_MODE_CONNECTED) + { + crtc_id = find_compatible_crtc(drm_fd.get(), resources, temp_connector); + if (crtc_id >= 0) + { + connector = std::move(temp_connector); + break; + } + } + } + + if (connector == nullptr) + { + WSI_LOG_ERROR("Failed to find connector for DRM device."); + return std::nullopt; + } + + uint32_t max_width = 0; + uint32_t max_height = 0; + util::vector display_modes{ allocator }; + + for (int j = 0; j < connector->count_modes; ++j) + { + /* Need the full drmModeModeInfo cached to supply to drmModeSetCrtc. */ + drm_display_mode mode{}; + mode.set_drm_mode(connector->modes[j]); + mode.set_preferred(connector->modes[j].type == DRM_MODE_TYPE_PREFERRED); + + uint32_t resolution = static_cast(mode.get_width()) * static_cast(mode.get_height()); + if (resolution >= max_width * max_height) + { + max_width = mode.get_width(); + max_height = mode.get_height(); + } + + if (!display_modes.try_push_back(mode)) + { + WSI_LOG_ERROR("Failed to allocate memory for display mode."); + return std::nullopt; + } + } + + util::unique_ptr display_modes_mem{ allocator.create(display_modes.size()) }; + if (display_modes_mem == nullptr) + { + WSI_LOG_ERROR("Failed to allocate memory for display mode vector."); + return std::nullopt; + } + + std::copy(display_modes.begin(), display_modes.end(), display_modes_mem.get()); + + auto display = + allocator.make_unique(std::move(drm_fd), crtc_id, std::move(connector), std::move(display_modes_mem), + display_modes.size(), max_width, max_height); + if (display == nullptr) + { + WSI_LOG_ERROR("Failed to allocate memory for display object."); + return std::nullopt; + } + + return display; +} + +drm_display_mode::drm_display_mode() + : m_display(nullptr) + , m_drm_mode_info{} + , m_preferred(false) +{ +} + +uint16_t drm_display_mode::get_width() const +{ + return m_drm_mode_info.hdisplay; +} + +uint16_t drm_display_mode::get_height() const +{ + return m_drm_mode_info.vdisplay; +} + +uint32_t drm_display_mode::get_refresh_rate() const +{ + /* DRM provides refresh rate in Hz and vulkan expects mHz */ + return m_drm_mode_info.vrefresh * 1000; +} + +void drm_display_mode::set_display(const drm_display *disp) +{ + m_display = disp; +} + +const drm_display *drm_display_mode::get_display() +{ + return m_display; +} + +drmModeModeInfo drm_display_mode::get_drm_mode() const +{ + return m_drm_mode_info; +} + +void drm_display_mode::set_drm_mode(drmModeModeInfo mode) +{ + m_drm_mode_info = mode; +} + +bool drm_display_mode::is_preferred() const +{ + return m_preferred; +} + +void drm_display_mode::set_preferred(bool preferred) +{ + m_preferred = preferred; +} + +drm_display_mode *drm_display::get_display_modes_begin() const +{ + return m_display_modes.get(); +} + +drm_display_mode *drm_display::get_display_modes_end() const +{ + return m_display_modes.get() + m_num_display_modes; +} + +size_t drm_display::get_num_display_modes() const +{ + return m_num_display_modes; +} + +int drm_display::get_drm_fd() const +{ + return m_drm_fd.get(); +} + +uint32_t drm_display::get_connector_id() const +{ + return m_drm_connector->connector_id; +} + +int drm_display::get_crtc_id() const +{ + return m_crtc_id; +} + +drmModeConnector *drm_display::get_connector() const +{ + return m_drm_connector.get(); +} + +uint32_t drm_display::get_max_width() const +{ + return m_max_width; +} +uint32_t drm_display::get_max_height() const +{ + return m_max_height; +} + +} /* namespace display */ + +} /* namespace wsi */ diff --git a/wsi/display/drm_display.hpp b/wsi/display/drm_display.hpp new file mode 100644 index 0000000..7415c9f --- /dev/null +++ b/wsi/display/drm_display.hpp @@ -0,0 +1,296 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "util/custom_allocator.hpp" +#include "util/file_descriptor.hpp" + +namespace wsi +{ + +namespace display +{ + +/* Owner types for DRM API objects */ +template +struct drm_deleter +{ + void operator()(T *object) + { + if (object != nullptr) + { + drm_object_free(object); + } + } +}; + +template +using drm_owner = std::unique_ptr>; +using drm_resources_owner = drm_owner<_drmModeRes, drmModeFreeResources>; +using drm_connector_owner = drm_owner<_drmModeConnector, drmModeFreeConnector>; +using drm_encoder_owner = drm_owner<_drmModeEncoder, drmModeFreeEncoder>; +using drm_plane_owner = drm_owner<_drmModePlane, drmModeFreePlane>; +using drm_plane_resources_owner = drm_owner<_drmModePlaneRes, drmModeFreePlaneResources>; +using drm_object_properties_owner = drm_owner<_drmModeObjectProperties, drmModeFreeObjectProperties>; +using drm_property_owner = drm_owner<_drmModeProperty, drmModeFreeProperty>; + +/* Forward declaration */ +class drm_display; + +/** + * @brief The display mode object. + * The drm_display_mode class stores information + * about a drm mode. + */ +class drm_display_mode +{ +public: + /** + * @brief drm_display_mode default constructor. + */ + drm_display_mode(); + + /** + * @brief Get the width of the display mode. + * + * @return Width of the display mode. + */ + uint16_t get_width() const; + + /** + * @brief Get the height of the display mode. + * + * @return Height of the display mode. + */ + uint16_t get_height() const; + + /** + * @brief Get the display mode refresh rate. + * + * @return Refresh rate of the display mode. + */ + uint32_t get_refresh_rate() const; + + /** + * @brief get function for display instance. + * + * @return The display instance. + */ + const drm_display *get_display(); + + /** + * @brief Sets the display instance. + * + * @param disp The display instance. + */ + void set_display(const drm_display *disp); + + /** + * @brief Get function for the drm mode. + */ + drmModeModeInfo get_drm_mode() const; + + /** + * @brief Sets the drm mode info. + * + * @param mode The drm mode. + */ + void set_drm_mode(drmModeModeInfo mode); + + /** + * @brief Check if this mode is the preferred mode for the connector. + * + * @return true if the mode is the preferred mode, otherwise false. + */ + bool is_preferred() const; + + /** + * @brief Set the preferred state. + * + * @param preferred The state to set. + */ + void set_preferred(bool preferred); + +private: + /** + * @brief the display instance. + */ + const drm_display *m_display; + + /** + * @brief Cached native drm mode. + */ + drmModeModeInfo m_drm_mode_info; + + /** + * @brief Flag for the preferred display mode. + */ + bool m_preferred = false; +}; + +/** + * @brief The vulkan's display object. + * The display class wraps a VkDisplayKHR. + */ +class drm_display +{ +public: + /** + * @brief Construct and initialize a display object. + * + * @param allocator The allocator object that the display will use. + * @return std::optional containing a display if initialization went well, otherwise std::nullopt. + */ + static std::optional> make_display(const util::allocator &allocator, + const char *drm_device); + + /** + * @brief display destructor. + */ + ~drm_display(); + + /** + * @brief Get the display modes begin pointer. + * + * @return drm_display_mode* pointer to the first display mode. + */ + drm_display_mode *get_display_modes_begin() const; + + /** + * @brief Get the display modes end pointer. + * + * @return drm_display_mode* pointer past the last display mode. + */ + drm_display_mode *get_display_modes_end() const; + + /** + * @brief Get number of display modes. + * + * @return const size_t number of display modes. + */ + size_t get_num_display_modes() const; + + /** + * @brief Get function for drm device file descriptor. + * + * @return The display device file descriptor. + */ + int get_drm_fd() const; + + /** + * @brief Get function for the connector id. + * + * @return The connector id. + */ + uint32_t get_connector_id() const; + + /** + * @brief Get connector corresponding to the current connector id. + * + * @return The connector. + */ + drmModeConnector *get_connector() const; + + /** + * @brief Get drm resources. + * + * @return The drm resources pointer. + */ + drmModeResPtr get_drm_resources() const; + + /** + * @brief Returns a CRTC compatible with this display's connector. + * + * @return The CRTC id. + */ + int get_crtc_id() const; + + /** + * @brief Get the max width of the display in pixels. + */ + uint32_t get_max_width() const; + + /** + * @brief Get the max height of the display in pixels. + */ + uint32_t get_max_height() const; + +private: + /* Allow util::allocator to access the private constructor */ + friend class util::allocator; + + /** + * @brief display constructor. + * + * @param allocator The allocator that the display will use. + */ + drm_display(util::fd_owner drm_fd, int crtc_id, drm_connector_owner drm_connector, + util::unique_ptr display_modes, size_t num_display_modes, uint32_t max_width, + uint32_t max_height); + + /** + * @brief File descriptor for the display device. + */ + util::fd_owner m_drm_fd; + + /** + * @brief Id of CRTC compatible with the chosen connector. + */ + int m_crtc_id; + + /** + * @brief Handle to the drm connector. + */ + drm_connector_owner m_drm_connector; + + /** + * @brief Pointer to available display modes for the connected display. + */ + util::unique_ptr m_display_modes; + + /** + * @brief Number of available display modes in @ref m_display_modes. + */ + size_t m_num_display_modes; + + /** + * @brief Maximum display resolution width. + */ + uint32_t m_max_width; + + /** + * @brief Maximum display resolution height. + */ + uint32_t m_max_height; +}; + +} /* namespace display */ + +} /* namespace wsi */ diff --git a/wsi/display/surface_properties.cpp b/wsi/display/surface_properties.cpp new file mode 100644 index 0000000..e020bdb --- /dev/null +++ b/wsi/display/surface_properties.cpp @@ -0,0 +1,509 @@ +/* + * 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. + */ + +#include +#include + +#include "surface_properties.hpp" +#include "util/macros.hpp" + +namespace wsi +{ + +namespace display +{ + +constexpr int max_core_1_0_formats = VK_FORMAT_ASTC_12x12_SRGB_BLOCK + 1; + +VkResult surface_properties::get_surface_capabilities(VkPhysicalDevice physical_device, + VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) +{ + get_surface_capabilities_common(physical_device, pSurfaceCapabilities); + + /* Image count limits */ + pSurfaceCapabilities->minImageCount = 2; + pSurfaceCapabilities->maxImageCount = 3; + + /* Composite alpha */ + pSurfaceCapabilities->supportedCompositeAlpha = + static_cast(VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR | VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + + return VK_SUCCESS; +} + +VkResult surface_properties::get_surface_formats(VkPhysicalDevice physical_device, uint32_t *surfaceFormatCount, + VkSurfaceFormatKHR *surfaceFormats, + VkSurfaceFormat2KHR *extended_surface_formats) +{ + int drm_fd = display->get_drm_fd(); + if (drm_fd == -1) + { + return VK_ERROR_SURFACE_LOST_KHR; + } + /* Allow userspace to query native primary plane information */ + if (drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) != 0) + { + return VK_ERROR_SURFACE_LOST_KHR; + } + + drm_plane_resources_owner plane_res{ drmModeGetPlaneResources(drm_fd) }; + if (plane_res == nullptr || plane_res->count_planes == 0) + { + return VK_ERROR_SURFACE_LOST_KHR; + } + + /* Look for the primary plane */ + drm_plane_owner plane{ nullptr }; + for (uint32_t i = 0; i < plane_res->count_planes; i++) + { + drm_plane_owner temp_plane{ drmModeGetPlane(drm_fd, plane_res->planes[i]) }; + if (temp_plane != nullptr) + { + drm_object_properties_owner props{ drmModeObjectGetProperties(drm_fd, plane_res->planes[i], + DRM_MODE_OBJECT_PLANE) }; + if (props != nullptr) + { + auto props_end = props->props + props->count_props; + auto prop = std::find_if(props->props, props_end, [drm_fd](auto &property_id) { + drm_property_owner prop{ drmModeGetProperty(drm_fd, property_id) }; + if (prop != nullptr && !strcmp(prop->name, "type")) + { + return true; + } + return false; + }); + if (prop != props_end) + { + auto index = std::distance(props->props, prop); + if (props->prop_values[index] == DRM_PLANE_TYPE_PRIMARY) + { + plane = std::move(temp_plane); + break; + } + } + } + } + } + + if (plane == nullptr) + { + WSI_LOG_ERROR("Failed to find primary plane."); + return VK_ERROR_SURFACE_LOST_KHR; + } + + uint32_t format_count = 0; + + /* If this happens, it is just wrong */ + assert(plane->count_formats > 0); + assert(plane->count_formats <= max_core_1_0_formats); + + std::array formats{}; + + for (uint32_t i = 0; i < plane->count_formats; ++i) + { + auto vk_format = util::drm::drm_to_vk_format(plane->formats[i]); + if (VK_FORMAT_UNDEFINED != vk_format) + { + formats[format_count] = surface_format_properties{ vk_format }; + VkPhysicalDeviceImageFormatInfo2KHR format_info = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR, + nullptr, + vk_format, + VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + 0 }; + VkResult res = formats[format_count].check_device_support(physical_device, format_info); + if (VK_SUCCESS == res) + { +#if WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN + if (layer::instance_private_data::get(physical_device).has_image_compression_support(physical_device)) + { + formats[format_count].add_device_compression_support(physical_device, format_info); + } +#endif + format_count++; + } + } + + /* Certain 8-bit UNORM formats can be interpreted as both UNORM and sRGB by Vulkan, so expose both formats. + * The colorSpace value is how the presentation engine interprets the format. + * The linearity of VkFormat and the display format may be different. + */ + auto vk_srgb_format = util::drm::drm_to_vk_srgb_format(plane->formats[i]); + if (VK_FORMAT_UNDEFINED != vk_srgb_format) + { + formats[format_count] = surface_format_properties{ vk_srgb_format }; + VkPhysicalDeviceImageFormatInfo2KHR format_info = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2_KHR, + nullptr, + vk_format, + VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + 0 }; + VkResult res = formats[format_count].check_device_support(physical_device, format_info); + if (VK_SUCCESS == res) + { +#if WSI_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN + if (layer::instance_private_data::get(physical_device).has_image_compression_support(physical_device)) + { + formats[format_count].add_device_compression_support(physical_device, format_info); + } +#endif + format_count++; + } + } + } + + return surface_properties_formats_helper(formats.begin(), formats.begin() + format_count, surfaceFormatCount, + surfaceFormats, extended_surface_formats); +} + +VkResult surface_properties::get_surface_present_modes(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *pPresentModeCount, VkPresentModeKHR *pPresentModes) +{ + UNUSED(physical_device); + UNUSED(surface); + + static const std::array modes = { VK_PRESENT_MODE_FIFO_KHR }; + + return get_surface_present_modes_common(pPresentModeCount, pPresentModes, modes); +} + +VWL_VKAPI_CALL(VkResult) +CreateDisplayModeKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, + const VkDisplayModeCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, + VkDisplayModeKHR *pMode) +{ + UNUSED(physicalDevice); + UNUSED(pAllocator); + + assert(display != VK_NULL_HANDLE); + assert(pMode != nullptr); + + assert(pCreateInfo != nullptr); + assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR); + assert(pCreateInfo->pNext == NULL); + assert(pCreateInfo->flags == 0); + + drm_display *dpy = reinterpret_cast(display); + + const VkDisplayModeParametersKHR *params = &pCreateInfo->parameters; + + if (params->visibleRegion.width == 0 || params->visibleRegion.height == 0 || params->refreshRate == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + auto *mode = std::find_if(dpy->get_display_modes_begin(), dpy->get_display_modes_end(), [params](auto &mode) { + return mode.get_width() == params->visibleRegion.width && mode.get_height() == params->visibleRegion.height && + mode.get_refresh_rate() == params->refreshRate; + }); + + if (mode != dpy->get_display_modes_end()) + { + *pMode = reinterpret_cast(mode); + + return VK_SUCCESS; + } + + return VK_ERROR_INITIALIZATION_FAILED; +} + +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 + + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +VWL_VKAPI_CALL(VkResult) +GetDisplayModePropertiesKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, + VkDisplayModePropertiesKHR *pProperties) +{ + UNUSED(physicalDevice); + assert(display != VK_NULL_HANDLE); + assert(pPropertyCount != nullptr); + + drm_display *dpy = reinterpret_cast(display); + assert(dpy != nullptr); + + drm_display_mode *modes{ dpy->get_display_modes_begin() }; + size_t num_modes{ dpy->get_num_display_modes() }; + + if (pProperties == nullptr) + { + *pPropertyCount = num_modes; + return VK_SUCCESS; + } + + uint32_t nr_properties = std::min(*pPropertyCount, static_cast(num_modes)); + *pPropertyCount = 0; + std::for_each(modes, modes + nr_properties, [&pProperties, &pPropertyCount](auto &mode) { + VkDisplayModePropertiesKHR properties = {}; + + VkDisplayModeKHR display_mode = reinterpret_cast(&mode); + properties.displayMode = display_mode; + + VkDisplayModeParametersKHR parameters{}; + parameters.visibleRegion = { mode.get_width(), mode.get_height() }; + parameters.refreshRate = mode.get_refresh_rate(); + properties.parameters = parameters; + + pProperties[*pPropertyCount] = properties; + *pPropertyCount += 1; + }); + + if (*pPropertyCount < static_cast(num_modes)) + { + return VK_INCOMPLETE; + } + + return VK_SUCCESS; +} + +VWL_VKAPI_CALL(VkResult) +GetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkDisplayModeKHR mode, uint32_t planeIndex, + VkDisplayPlaneCapabilitiesKHR *pCapabilities) +{ + assert(physicalDevice != VK_NULL_HANDLE); + assert(mode != VK_NULL_HANDLE); + assert(pCapabilities != nullptr); + + drm_display_mode *display_mode = reinterpret_cast(mode); + assert(display_mode != nullptr); + auto dpy = display_mode->get_display(); + assert(dpy != nullptr); + + /* Implementation supports only one plane for presenting + * images. Therefore plane index must be 0. */ + assert(planeIndex == 0); + + auto valid_mode = + std::find_if(dpy->get_display_modes_begin(), dpy->get_display_modes_end(), [&display_mode](auto &mode) { + return (display_mode->get_width() == mode.get_width()) && (display_mode->get_height() == mode.get_height()) && + (display_mode->get_refresh_rate() == mode.get_refresh_rate()); + }); + + assert(valid_mode != dpy->get_display_modes_end()); + + VkDisplayPlaneCapabilitiesKHR planeCapabilities{}; + planeCapabilities.supportedAlpha = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + planeCapabilities.minSrcPosition = { 0, 0 }; + planeCapabilities.maxSrcPosition = { 0, 0 }; + /* Implementation allows swapchains to be a subset of the display area. */ + planeCapabilities.minSrcExtent = { 0, 0 }; + planeCapabilities.maxSrcExtent = { display_mode->get_width(), display_mode->get_height() }; + planeCapabilities.minDstPosition = { 0, 0 }; + planeCapabilities.maxDstPosition = { 0, 0 }; + planeCapabilities.minDstExtent = { display_mode->get_width(), display_mode->get_height() }; + planeCapabilities.maxDstExtent = { display_mode->get_width(), display_mode->get_height() }; + + *pCapabilities = planeCapabilities; + + return VK_SUCCESS; +} + +VWL_VKAPI_CALL(VkResult) +GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice physicalDevice, uint32_t planeIndex, uint32_t *pDisplayCount, + VkDisplayKHR *pDisplays) +{ + assert(physicalDevice != VK_NULL_HANDLE); + assert(pDisplayCount != nullptr); + + std::shared_ptr dpy = surface_properties::get_instance().get_display(); + + assert(dpy != nullptr); + + /* Implementation supports only one plane for presenting + * images. Therefore plane index must be 0. */ + assert(planeIndex == 0); + + if (pDisplays == nullptr) + { + /* Implementation will expose just one (the main) + * plane for the application to use. */ + *pDisplayCount = 1; + return VK_SUCCESS; + } + + if (*pDisplayCount == 0) + { + return VK_INCOMPLETE; + } + + *pDisplays = reinterpret_cast(dpy.get()); + *pDisplayCount = 1; + + return VK_SUCCESS; +} + +VWL_VKAPI_CALL(VkResult) +GetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, + VkDisplayPlanePropertiesKHR *pProperties) +{ + assert(physicalDevice != VK_NULL_HANDLE); + assert(pPropertyCount != nullptr); + + std::shared_ptr dpy = surface_properties::get_instance().get_display(); + + assert(dpy != nullptr); + + if (pProperties == nullptr) + { + /* Implementation will expose just one (the main) + * plane for the application to use. */ + *pPropertyCount = 1; + return VK_SUCCESS; + } + + if (*pPropertyCount == 0) + { + return VK_INCOMPLETE; + } + + VkDisplayPlanePropertiesKHR planeProperties{}; + planeProperties.currentDisplay = reinterpret_cast(dpy.get()); + + /* Since the implementation is exposing just one plane the value for + * the current stack index must be 0.*/ + planeProperties.currentStackIndex = 0; + + *pProperties = planeProperties; + *pPropertyCount = 1; + + return VK_SUCCESS; +} + +VWL_VKAPI_CALL(VkResult) +GetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, + VkDisplayPropertiesKHR *pProperties) +{ + assert(physicalDevice != VK_NULL_HANDLE); + assert(pPropertyCount != nullptr); + + std::shared_ptr dpy = surface_properties::get_instance().get_display(); + + if (dpy == nullptr) + { + *pPropertyCount = 0; + return VK_SUCCESS; + } + + if (pProperties == nullptr) + { + *pPropertyCount = 1; + return VK_SUCCESS; + } + + if (*pPropertyCount == 0) + { + return VK_INCOMPLETE; + } + + *pPropertyCount = 1; + + VkDisplayPropertiesKHR display_properties = {}; + display_properties.display = reinterpret_cast(dpy.get()); + display_properties.displayName = "DRM display"; + display_properties.physicalDimensions = { dpy->get_connector()->mmWidth, dpy->get_connector()->mmHeight }; + display_properties.physicalResolution = { dpy->get_max_width(), dpy->get_max_height() }; + display_properties.supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + display_properties.planeReorderPossible = VK_FALSE; + display_properties.persistentContent = VK_FALSE; + + *pProperties = display_properties; + + return VK_SUCCESS; +} + +PFN_vkVoidFunction surface_properties::get_proc_addr(const char *name) +{ + + if (strcmp(name, "vkCreateDisplayModeKHR") == 0) + { + return reinterpret_cast(CreateDisplayModeKHR); + } + else if (strcmp(name, "vkCreateDisplayPlaneSurfaceKHR") == 0) + { + return reinterpret_cast(CreateDisplayPlaneSurfaceKHR); + } + else if (strcmp(name, "vkGetDisplayModePropertiesKHR") == 0) + { + return reinterpret_cast(GetDisplayModePropertiesKHR); + } + else if (strcmp(name, "vkGetDisplayPlaneCapabilitiesKHR") == 0) + { + return reinterpret_cast(GetDisplayPlaneCapabilitiesKHR); + } + else if (strcmp(name, "vkGetDisplayPlaneSupportedDisplaysKHR") == 0) + { + return reinterpret_cast(GetDisplayPlaneSupportedDisplaysKHR); + } + else if (strcmp(name, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR") == 0) + { + return reinterpret_cast(GetPhysicalDeviceDisplayPlanePropertiesKHR); + } + else if (strcmp(name, "vkGetPhysicalDeviceDisplayPropertiesKHR") == 0) + { + return reinterpret_cast(GetPhysicalDeviceDisplayPropertiesKHR); + } + + return nullptr; +} + +VkResult surface_properties::get_required_instance_extensions(util::extension_list &extension_list) +{ + const std::array required_instance_extensions{ + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + }; + + return extension_list.add(required_instance_extensions.data(), required_instance_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); +} + +surface_properties &surface_properties::get_instance() +{ + static surface_properties instance; + + return instance; +} + +std::shared_ptr surface_properties::get_display() +{ + return display; +} + +} /* namespace display */ + +} /* namespace wsi */ \ No newline at end of file diff --git a/wsi/display/surface_properties.hpp b/wsi/display/surface_properties.hpp new file mode 100644 index 0000000..285ab4a --- /dev/null +++ b/wsi/display/surface_properties.hpp @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "wsi/surface_properties.hpp" +#include "drm_display.hpp" + +namespace wsi +{ + +namespace display +{ + +class surface_properties : public wsi::surface_properties +{ +public: + VkResult get_surface_capabilities(VkPhysicalDevice physical_device, + VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) override; + + VkResult get_surface_formats(VkPhysicalDevice physical_device, uint32_t *surfaceFormatCount, + VkSurfaceFormatKHR *surfaceFormats, + VkSurfaceFormat2KHR *extended_surface_formats) override; + + VkResult get_surface_present_modes(VkPhysicalDevice physical_device, VkSurfaceKHR surface, + uint32_t *pPresentModeCount, VkPresentModeKHR *pPresentModes) override; + + PFN_vkVoidFunction get_proc_addr(const char *name) override; + + VkResult get_required_instance_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(); + + std::shared_ptr get_display(); + +private: + std::shared_ptr display; +}; + +} /* namespace display */ + +} /* namespace wsi */ \ No newline at end of file diff --git a/wsi/headless/surface_properties.cpp b/wsi/headless/surface_properties.cpp index 35891af..ca45f38 100644 --- a/wsi/headless/surface_properties.cpp +++ b/wsi/headless/surface_properties.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019, 2021-2023 Arm Limited. + * Copyright (c) 2017-2019, 2021-2024 Arm Limited. * * SPDX-License-Identifier: MIT * @@ -39,8 +39,6 @@ #include "surface.hpp" #include "util/macros.hpp" -#define UNUSED(x) ((void)(x)) - namespace wsi { namespace headless diff --git a/wsi/wsi_factory.cpp b/wsi/wsi_factory.cpp index c50b09f..4e94287 100644 --- a/wsi/wsi_factory.cpp +++ b/wsi/wsi_factory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2023 Arm Limited. + * Copyright (c) 2019-2024 Arm Limited. * * SPDX-License-Identifier: MIT * @@ -46,6 +46,10 @@ #include "wayland/surface_properties.hpp" #endif +#if BUILD_WSI_DISPLAY +#include "display/surface_properties.hpp" +#endif + namespace wsi { @@ -60,6 +64,9 @@ static struct wsi_extension #if BUILD_WSI_WAYLAND { { VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_SPEC_VERSION }, VK_ICD_WSI_PLATFORM_WAYLAND }, #endif +#if BUILD_WSI_DISPLAY + { { VK_KHR_DISPLAY_EXTENSION_NAME, VK_KHR_DISPLAY_SPEC_VERSION }, VK_ICD_WSI_PLATFORM_DISPLAY }, +#endif }; static surface_properties *get_surface_properties(VkIcdWsiPlatform platform) @@ -73,6 +80,10 @@ static surface_properties *get_surface_properties(VkIcdWsiPlatform platform) #if BUILD_WSI_WAYLAND case VK_ICD_WSI_PLATFORM_WAYLAND: return &wayland::surface_properties::get_instance(); +#endif +#if BUILD_WSI_DISPLAY + case VK_ICD_WSI_PLATFORM_DISPLAY: + return &display::surface_properties::get_instance(); #endif default: return nullptr;