Refactor framebuffer format support

Add support for format modifiers in framebuffers using
drmModeAddFB2WithModifiers.
Add compilation flag to force VK_KHR_display to not support format
modifiers for framebuffers.

Change-Id: Ie8eb2e96e543a15ab3534a7e73425f277a4b7d58
Signed-off-by: Dennis Wildmark <dennis.wildmark@arm.com>
This commit is contained in:
Dennis Wildmark 2024-04-11 10:05:24 +02:00
parent 23cd21c99c
commit 6c3f4e9326
10 changed files with 313 additions and 109 deletions

View file

@ -58,6 +58,7 @@ 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)
option(BUILD_WSI_DISPLAY_SUPPORT_FORMAT_MODIFIERS "Build with support for format modifiers in VK_KHR_display" ON)
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")
@ -244,6 +245,12 @@ else()
list(APPEND JSON_COMMANDS COMMAND sed -i '/VK_EXT_image_compression_control_swapchain/d' ${CMAKE_CURRENT_BINARY_DIR}/VkLayer_window_system_integration.json)
endif()
if (BUILD_WSI_DISPLAY_SUPPORT_FORMAT_MODIFIERS)
add_definitions("-DWSI_DISPLAY_SUPPORT_FORMAT_MODIFIERS=1")
else()
add_definitions("-DWSI_DISPLAY_SUPPORT_FORMAT_MODIFIERS=0")
endif()
target_link_libraries(${PROJECT_NAME} ${LINK_WSI_LIBS})
add_custom_target(manifest_json ALL COMMAND

View file

@ -24,6 +24,7 @@
#include "drm_display.hpp"
#include "util/custom_allocator.hpp"
#include "wsi/surface.hpp"
#include <cstdlib>
#include <cstring>
@ -33,6 +34,7 @@
#include <unistd.h>
#include <assert.h>
#include <mutex>
#include <drm_fourcc.h>
namespace wsi
{
@ -42,15 +44,18 @@ namespace display
const std::string default_dri_device_name{ "/dev/dri/card0" };
drm_display::drm_display(util::fd_owner drm_fd, int crtc_id, drm_connector_owner drm_connector,
util::unique_ptr<util::vector<drm_format_pair>> supported_formats,
util::unique_ptr<drm_display_mode> display_modes, size_t num_display_modes, uint32_t max_width,
uint32_t max_height)
uint32_t max_height, bool supports_fb_modifiers)
: m_drm_fd(std::move(drm_fd))
, m_crtc_id(crtc_id)
, m_drm_connector(std::move(drm_connector))
, m_supported_formats(std::move(supported_formats))
, m_display_modes(std::move(display_modes))
, m_num_display_modes(num_display_modes)
, m_max_width(max_width)
, m_max_height(max_height)
, m_supports_fb_modifiers(supports_fb_modifiers)
{
}
@ -103,6 +108,100 @@ static int find_compatible_crtc(int fd, drm_resources_owner &resources, drm_conn
return -ENODEV;
}
static bool find_primary_plane(const util::fd_owner &drm_fd, const drm_plane_resources_owner &plane_res,
drm_plane_owner &primary_plane, uint32_t &primary_plane_index)
{
for (uint32_t i = 0; i < plane_res->count_planes; i++)
{
drm_plane_owner temp_plane{ drmModeGetPlane(drm_fd.get(), plane_res->planes[i]) };
if (temp_plane != nullptr)
{
drm_object_properties_owner props{ drmModeObjectGetProperties(drm_fd.get(), plane_res->planes[i],
DRM_MODE_OBJECT_PLANE) };
if (props == nullptr)
{
continue;
}
for (uint32_t j = 0; j < props->count_props; j++)
{
drm_property_owner prop{ drmModeGetProperty(drm_fd.get(), props->props[j]) };
if (prop == nullptr)
{
continue;
}
if (!strcmp(prop->name, "type"))
{
if (props->prop_values[j] == DRM_PLANE_TYPE_PRIMARY)
{
primary_plane = std::move(temp_plane);
primary_plane_index = i;
return true;
}
}
}
}
}
return false;
}
static bool fill_supported_formats(const drm_plane_owner &primary_plane,
util::vector<drm_format_pair> &supported_formats)
{
for (uint32_t i = 0; i < primary_plane->count_formats; i++)
{
if (!supported_formats.try_push_back(drm_format_pair{ primary_plane->formats[i], DRM_FORMAT_MOD_LINEAR }))
{
WSI_LOG_ERROR("Out of host memory.");
return false;
}
}
return true;
}
static bool fill_supported_formats_with_modifiers(uint32_t primary_plane_index, const util::fd_owner &drm_fd,
const drm_plane_resources_owner &plane_res,
util::vector<drm_format_pair> &supported_formats)
{
drm_object_properties_owner object_properties{ drmModeObjectGetProperties(
drm_fd.get(), plane_res->planes[primary_plane_index], DRM_MODE_OBJECT_PLANE) };
if (object_properties == nullptr)
{
return false;
}
for (uint32_t i = 0; i < object_properties->count_props; i++)
{
drm_property_owner property{ drmModeGetProperty(drm_fd.get(), object_properties->props[i]) };
if (property == nullptr)
{
continue;
}
if (!strcmp(property->name, "IN_FORMATS"))
{
drmModeFormatModifierIterator iter{};
drm_property_blob_owner blob{ drmModeGetPropertyBlob(drm_fd.get(), object_properties->prop_values[i]) };
if (blob == nullptr)
{
return false;
}
while (drmModeFormatModifierBlobIterNext(blob.get(), &iter))
{
if (!supported_formats.try_push_back(drm_format_pair{ iter.fmt, iter.mod }))
{
return false;
}
}
}
}
return true;
}
std::optional<drm_display> 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) };
@ -184,11 +283,66 @@ std::optional<drm_display> drm_display::make_display(const util::allocator &allo
return std::nullopt;
}
/* Allow userspace to query native primary plane information */
if (drmSetClientCap(drm_fd.get(), DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) != 0)
{
return std::nullopt;
}
drm_plane_resources_owner plane_res{ drmModeGetPlaneResources(drm_fd.get()) };
if (plane_res == nullptr || plane_res->count_planes == 0)
{
return std::nullopt;
}
uint32_t primary_plane_index = std::numeric_limits<uint32_t>::max();
drm_plane_owner primary_plane{ nullptr };
if (!find_primary_plane(drm_fd, plane_res, primary_plane, primary_plane_index))
{
WSI_LOG_ERROR("Failed to find primary plane for display.");
return std::nullopt;
}
assert(primary_plane != nullptr);
assert(primary_plane_index != std::numeric_limits<uint32_t>::max());
bool supports_fb_modifiers = false;
#if WSI_DISPLAY_SUPPORT_FORMAT_MODIFIERS
uint64_t addfb2_modifier_support = 0;
if (drmGetCap(drm_fd.get(), DRM_CAP_ADDFB2_MODIFIERS, &addfb2_modifier_support) == 0)
{
supports_fb_modifiers = addfb2_modifier_support;
}
#endif
auto supported_formats = allocator.make_unique<util::vector<drm_format_pair>>(allocator);
if (supports_fb_modifiers)
{
if (!fill_supported_formats_with_modifiers(primary_plane_index, drm_fd, plane_res, *supported_formats))
{
/* Fall back to the linear formats */
if (!fill_supported_formats(primary_plane, *supported_formats))
{
return std::nullopt;
}
}
}
else
{
if (!fill_supported_formats(primary_plane, *supported_formats))
{
return std::nullopt;
}
}
std::copy(display_modes.begin(), display_modes.end(), display_modes_mem.get());
drm_display display{
std::move(drm_fd), crtc_id, std::move(connector), std::move(display_modes_mem), display_modes.size(),
max_width, max_height
std::move(drm_fd), crtc_id, std::move(connector), std::move(supported_formats), std::move(display_modes_mem),
display_modes.size(), max_width, max_height, supports_fb_modifiers
};
return std::make_optional(std::move(display));
@ -211,6 +365,26 @@ std::optional<drm_display> &drm_display::get_display()
return display;
}
const util::vector<drm_format_pair> *drm_display::get_supported_formats() const
{
return m_supported_formats.get();
}
bool drm_display::is_format_supported(const drm_format_pair &format) const
{
auto supported_format =
std::find_if(m_supported_formats->begin(), m_supported_formats->end(), [format](const auto &supported_format) {
return format.fourcc == supported_format.fourcc && format.modifier == supported_format.modifier;
});
return supported_format != m_supported_formats->end();
}
bool drm_display::supports_fb_modifiers() const
{
return m_supports_fb_modifiers;
}
drm_display_mode::drm_display_mode()
: m_drm_mode_info{}
, m_preferred(false)

View file

@ -31,6 +31,7 @@
#include "util/custom_allocator.hpp"
#include "util/file_descriptor.hpp"
#include "wsi/surface.hpp"
namespace wsi
{
@ -60,6 +61,7 @@ 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>;
using drm_property_blob_owner = drm_owner<_drmModePropertyBlob, drmModeFreePropertyBlob>;
/**
* @brief Owner class for an array of DRM GEM buffer handles.
@ -249,6 +251,28 @@ public:
*/
drmModeResPtr get_drm_resources() const;
/**
* @brief Get the supported formats for the display.
*
* @return Pointer to vector of supported formats.
*/
const util::vector<drm_format_pair> *get_supported_formats() const;
/**
* @brief Query the display for support for adding framebuffers with format modifiers.
*
* @return true if supported, otherwise false.
*/
bool supports_fb_modifiers() const;
/**
* @brief Query the display for support of a specific format and modifier combination.
*
* @param format The format to query support for.
* @return true if the format is supported by the display, otherwise false.
*/
bool is_format_supported(const drm_format_pair &format) const;
/**
* @brief Returns a CRTC compatible with this display's connector.
*
@ -273,8 +297,9 @@ private:
* @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<util::vector<drm_format_pair>> supported_formats,
util::unique_ptr<drm_display_mode> display_modes, size_t num_display_modes, uint32_t max_width,
uint32_t max_height);
uint32_t max_height, bool supports_fb_modifiers);
/**
* @brief File descriptor for the display device.
@ -291,6 +316,11 @@ private:
*/
drm_connector_owner m_drm_connector;
/**
* @brief Vector of supported formats for use with the display.
*/
util::unique_ptr<util::vector<drm_format_pair>> m_supported_formats;
/**
* @brief Pointer to available display modes for the connected display.
*/
@ -310,6 +340,11 @@ private:
* @brief Maximum display resolution height.
*/
uint32_t m_max_height;
/**
* @brief Flag to indicate if the display supports framebuffers with format modifiers.
*/
bool m_supports_fb_modifiers;
};
} /* namespace display */

View file

@ -35,8 +35,9 @@ namespace wsi
namespace display
{
surface::surface(drm_display_mode *display_mode)
surface::surface(drm_display_mode *display_mode, VkExtent2D extent)
: m_display_mode(display_mode)
, m_extent(extent)
, m_surface_properties(*this)
{
}
@ -53,14 +54,9 @@ util::unique_ptr<swapchain_base> surface::allocate_swapchain(layer::device_priva
return util::unique_ptr<swapchain_base>(alloc.make_unique<swapchain>(dev_data, allocator, *this));
}
VkExtent2D surface::get_current_extent() const
VkExtent2D surface::get_extent() const
{
return m_current_extent;
}
void surface::set_current_extent(VkExtent2D extent)
{
m_current_extent = extent;
return m_extent;
}
drm_display_mode *surface::get_display_mode()

View file

@ -40,18 +40,42 @@ namespace display
class surface : public wsi::surface
{
public:
surface(drm_display_mode *mode);
/**
* @brief Construct a new surface.
*
* @param mode The display mode to be used with the surface.
* @param extent The extent of the surface.
*/
surface(drm_display_mode *mode, VkExtent2D extent);
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);
/**
* @brief Get the extent of the surface.
*/
VkExtent2D get_extent() const;
/**
* @brief Get the display mode associated with this surface.
*/
drm_display_mode *get_display_mode();
private:
/**
* @brief Pointer to the DRM display mode used with this surface.
*/
drm_display_mode *m_display_mode;
VkExtent2D m_current_extent;
/**
* @brief The extent of this surface.
*/
VkExtent2D m_extent;
/**
* @brief Surface properties instance specific to this surface.
*/
surface_properties m_surface_properties;
};

View file

@ -54,9 +54,9 @@ VkResult surface_properties::get_surface_capabilities(VkPhysicalDevice physical_
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();
pSurfaceCapabilities->currentExtent = m_specific_surface->get_extent();
pSurfaceCapabilities->minImageExtent = m_specific_surface->get_extent();
pSurfaceCapabilities->maxImageExtent = m_specific_surface->get_extent();
}
/* Image count limits */
@ -80,73 +80,18 @@ VkResult surface_properties::get_surface_formats(VkPhysicalDevice physical_devic
return VK_ERROR_SURFACE_LOST_KHR;
}
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;
}
auto display_formats = display->get_supported_formats();
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);
assert(display_formats->size() > 0);
assert(display_formats->size() <= max_core_1_0_formats);
std::array<surface_format_properties, max_core_1_0_formats> formats{};
for (uint32_t i = 0; i < plane->count_formats; ++i)
for (const auto &drm_format : *display_formats)
{
auto vk_format = util::drm::drm_to_vk_format(plane->formats[i]);
auto vk_format = util::drm::drm_to_vk_format(drm_format.fourcc);
if (VK_FORMAT_UNDEFINED != vk_format)
{
formats[format_count] = surface_format_properties{ vk_format };
@ -174,7 +119,7 @@ VkResult surface_properties::get_surface_formats(VkPhysicalDevice physical_devic
* 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]);
auto vk_srgb_format = util::drm::drm_to_vk_srgb_format(drm_format.fourcc);
if (VK_FORMAT_UNDEFINED != vk_srgb_format)
{
formats[format_count] = surface_format_properties{ vk_srgb_format };
@ -263,17 +208,16 @@ CreateDisplayPlaneSurfaceKHR(VkInstance instance, const VkDisplaySurfaceCreateIn
drm_display_mode *display_mode = reinterpret_cast<drm_display_mode *>(pCreateInfo->displayMode);
auto wsi_surface = allocator.make_unique<surface>(display_mode);
VkResult res = instance_data.disp.CreateDisplayPlaneSurfaceKHR(instance, pCreateInfo, pAllocator, pSurface);
if (res == VK_SUCCESS)
{
auto wsi_surface = allocator.make_unique<surface>(display_mode, pCreateInfo->imageExtent);
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)

View file

@ -108,12 +108,18 @@ VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info
TRY_LOG(util::get_drm_format_properties(m_device_data.physical_device, info.format, drm_format_props),
"Failed to get format properties");
auto &display = drm_display::get_display();
if (!display.has_value())
{
WSI_LOG_ERROR("DRM display not available.");
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
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))
if (!display->is_format_supported(drm_format))
{
continue;
}
@ -315,6 +321,9 @@ VkResult swapchain::create_framebuffer(const VkImageCreateInfo &image_create_inf
{
VkResult ret_code = VK_SUCCESS;
std::array<uint32_t, util::MAX_PLANES> strides{ 0, 0, 0, 0 };
std::array<uint64_t, util::MAX_PLANES> modifiers{ 0, 0, 0, 0 };
const drm_format_pair allocated_format{ m_image_creation_parameters.m_allocated_format.fourcc,
m_image_creation_parameters.m_allocated_format.modifier };
auto &display = drm_display::get_display();
if (!display.has_value())
@ -330,6 +339,7 @@ VkResult swapchain::create_framebuffer(const VkImageCreateInfo &image_create_inf
{
assert(image_data->external_mem.get_strides()[plane] > 0);
strides[plane] = image_data->external_mem.get_strides()[plane];
modifiers[plane] = allocated_format.modifier;
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));
@ -337,9 +347,26 @@ VkResult swapchain::create_framebuffer(const VkImageCreateInfo &image_create_inf
}
}
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 (!display->is_format_supported(allocated_format))
{
WSI_LOG_ERROR("Format not supported.");
return VK_ERROR_INITIALIZATION_FAILED;
}
int error = 0;
if (display->supports_fb_modifiers())
{
error = drmModeAddFB2WithModifiers(
display->get_drm_fd(), image_create_info.extent.width, image_create_info.extent.height,
allocated_format.fourcc, buffer_handles.data(), strides.data(), image_data->external_mem.get_offsets().data(),
modifiers.data(), &image_data->fb_id, DRM_MODE_FB_MODIFIERS);
}
else
{
error = drmModeAddFB2(display->get_drm_fd(), image_create_info.extent.width, image_create_info.extent.height,
allocated_format.fourcc, buffer_handles.data(), strides.data(),
image_data->external_mem.get_offsets().data(), &image_data->fb_id, 0);
}
if (error != 0)
{

View file

@ -75,12 +75,6 @@ struct image_creation_parameters
}
};
struct drm_format_pair
{
uint32_t fourcc;
uint64_t modifier;
};
/**
* @brief Display swapchain class.
*/

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Arm Limited.
* Copyright (c) 2021, 2024 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
@ -36,6 +36,15 @@
namespace wsi
{
/**
* @brief Struct describing a DRM format with modifier.
*/
struct drm_format_pair
{
uint32_t fourcc;
uint64_t modifier;
};
/**
* @brief A generic WSI representation of a VkSurface.
*

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 Arm Limited.
* Copyright (c) 2021, 2024 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
@ -40,12 +40,6 @@ namespace wsi
namespace wayland
{
struct drm_format_pair
{
uint32_t fourcc;
uint64_t modifier;
};
/**
* Wayland callback for global wl_registry events to handle global objects required by @ref wsi::wayland::surface
*/
@ -60,7 +54,7 @@ public:
struct init_parameters;
/** Constructor to allow for custom allocation, but require privately defined arguments. */
surface(const init_parameters&);
surface(const init_parameters &);
/**
* @brief Allocates and initializes a surface
@ -109,7 +103,7 @@ public:
*
* The raw pointer is valid for the lifetime of the surface.
*/
zwp_linux_surface_synchronization_v1* get_surface_sync_interface()
zwp_linux_surface_synchronization_v1 *get_surface_sync_interface()
{
return surface_sync_interface.get();
}
@ -166,7 +160,7 @@ private:
/** Container for the zwp_linux_explicit_synchronization_v1 interface binding */
wayland_owner<zwp_linux_explicit_synchronization_v1> explicit_sync_interface;
/** Container for the surface specific zwp_linux_surface_synchronization_v1 interface. */
/** Container for the surface specific zwp_linux_surface_synchronization_v1 interface. */;
wayland_owner<zwp_linux_surface_synchronization_v1> surface_sync_interface;
/**