vulkan-wsi-layer/wsi/wayland/swapchain.cpp

643 lines
23 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2017-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.
*/
#define VK_USE_PLATFORM_WAYLAND_KHR 1
#include "swapchain.hpp"
#include "wl_helpers.hpp"
#include "surface_properties.hpp"
#include "layer/wsi_layer_experimental.hpp"
#include <cstdint>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <cstdlib>
#include <cerrno>
#include <cstdio>
#include <climits>
#include <functional>
#include "util/drm/drm_utils.hpp"
#include "util/log.hpp"
#include "util/helpers.hpp"
#include "util/macros.hpp"
#include "util/format_modifiers.hpp"
namespace wsi
{
namespace wayland
{
swapchain::swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator,
surface &wsi_surface)
: swapchain_base(dev_data, pAllocator)
, m_display(wsi_surface.get_wl_display())
, m_surface(wsi_surface.get_wl_surface())
, m_wsi_surface(&wsi_surface)
, m_buffer_queue(nullptr)
, m_wsi_allocator(nullptr)
, m_image_creation_parameters({}, m_allocator, {}, {})
{
m_image_create_info.format = VK_FORMAT_UNDEFINED;
}
swapchain::~swapchain()
{
teardown();
if (m_wsi_allocator != nullptr)
{
wsialloc_delete(m_wsi_allocator);
}
m_wsi_allocator = nullptr;
if (m_buffer_queue != nullptr)
{
wl_event_queue_destroy(m_buffer_queue);
}
}
VkResult swapchain::init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info,
bool &use_presentation_thread)
{
#if VULKAN_WSI_LAYER_EXPERIMENTAL
std::array<util::unique_ptr<wsi::vulkan_time_domain>, 1> time_domains_array = {
m_allocator.make_unique<wsi::vulkan_time_domain>(VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT,
VK_TIME_DOMAIN_DEVICE_KHR),
};
for (auto &time_domain : time_domains_array)
{
if (!m_time_domains.m_time_domains.try_push_back(std::move(time_domain)))
{
WSI_LOG_ERROR("Failed to add a time domain to m_time_domains.");
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
}
#endif
if ((m_display == nullptr) || (m_surface == nullptr) || (m_wsi_surface->get_dmabuf_interface() == nullptr))
{
return VK_ERROR_INITIALIZATION_FAILED;
}
m_buffer_queue = wl_display_create_queue(m_display);
if (m_buffer_queue == nullptr)
{
WSI_LOG_ERROR("Failed to create buffer wl queue.");
return VK_ERROR_INITIALIZATION_FAILED;
}
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;
}
/*
* When VK_PRESENT_MODE_MAILBOX_KHR has been chosen by the application we don't
* initialize the page flip thread so the present_image function can be called
* during vkQueuePresent.
*/
use_presentation_thread =
WAYLAND_FIFO_PRESENTATION_THREAD_ENABLED && (m_present_mode != VK_PRESENT_MODE_MAILBOX_KHR);
return VK_SUCCESS;
}
VWL_CAPI_CALL(void) buffer_release(void *data, struct wl_buffer *wayl_buffer) VWL_API_POST
{
auto sc = reinterpret_cast<swapchain *>(data);
sc->release_buffer(wayl_buffer);
}
void swapchain::release_buffer(struct wl_buffer *wayl_buffer)
{
uint32_t i;
for (i = 0; i < m_swapchain_images.size(); i++)
{
auto data = reinterpret_cast<wayland_image_data *>(m_swapchain_images[i].data);
if (data && data->buffer == wayl_buffer)
{
unpresent_image(i);
break;
}
}
/* check we found a buffer to unpresent */
assert(i < m_swapchain_images.size());
}
static struct wl_buffer_listener buffer_listener = { buffer_release };
VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info,
util::vector<wsialloc_format> &importable_formats,
util::vector<uint64_t> &exportable_modifers,
util::vector<VkDrmFormatModifierPropertiesEXT> &drm_format_props)
{
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)
{
bool is_supported = false;
drm_format_pair drm_format{ util::drm::vk_to_drm_format(info.format), prop.drmFormatModifier };
for (const auto &format : m_wsi_surface->get_formats())
{
if (format.fourcc == drm_format.fourcc && format.modifier == drm_format.modifier)
{
is_supported = true;
break;
}
}
if (!is_supported)
{
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, wayland_image_data *image_data,
util::vector<wsialloc_format> &importable_formats,
wsialloc_format *allocated_format, bool avoid_allocation)
{
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 (avoid_allocation)
{
allocation_flags |= WSIALLOC_ALLOCATE_NO_MEMORY;
}
#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 };
wsialloc_allocate_result alloc_result = { 0 };
/* Clear buffer_fds and average_row_strides for error purposes */
for (int i = 0; i < WSIALLOC_MAX_PLANES; ++i)
{
alloc_result.buffer_fds[i] = -1;
alloc_result.average_row_strides[i] = -1;
}
const auto res = wsialloc_alloc(m_wsi_allocator, &alloc_info, &alloc_result);
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;
}
*allocated_format = alloc_result.format;
auto &external_memory = image_data->external_mem;
external_memory.set_strides(alloc_result.average_row_strides);
external_memory.set_buffer_fds(alloc_result.buffer_fds);
external_memory.set_offsets(alloc_result.offsets);
uint32_t num_planes = util::drm::drm_fourcc_format_get_num_planes(alloc_result.format.fourcc);
if (!avoid_allocation)
{
uint32_t num_memory_planes = 0;
for (uint32_t i = 0; i < num_planes; ++i)
{
auto it = std::find(std::begin(alloc_result.buffer_fds) + i + 1, std::end(alloc_result.buffer_fds),
alloc_result.buffer_fds[i]);
if (it == std::end(alloc_result.buffer_fds))
{
num_memory_planes++;
}
}
assert(alloc_result.is_disjoint == (num_memory_planes > 1));
external_memory.set_num_memories(num_memory_planes);
}
external_memory.set_format_info(alloc_result.is_disjoint, num_planes);
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,
wayland_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, wayland_image_data *image_data)
{
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 (!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, false));
return VK_SUCCESS;
}
VkResult swapchain::create_wl_buffer(const VkImageCreateInfo &image_create_info, swapchain_image &image,
wayland_image_data *image_data)
{
/* create a wl_buffer using the dma_buf protocol */
zwp_linux_buffer_params_v1 *params = zwp_linux_dmabuf_v1_create_params(m_wsi_surface->get_dmabuf_interface());
uint32_t modifier_hi = m_image_creation_parameters.m_allocated_format.modifier >> 32;
uint32_t modifier_low = m_image_creation_parameters.m_allocated_format.modifier & 0xFFFFFFFF;
for (uint32_t plane = 0; plane < image_data->external_mem.get_num_planes(); plane++)
{
zwp_linux_buffer_params_v1_add(params, image_data->external_mem.get_buffer_fds()[plane], plane,
image_data->external_mem.get_offsets()[plane],
image_data->external_mem.get_strides()[plane], modifier_hi, modifier_low);
}
const auto fourcc = util::drm::vk_to_drm_format(image_create_info.format);
assert(image_create_info.extent.width <= INT32_MAX);
assert(image_create_info.extent.height <= INT32_MAX);
image_data->buffer = zwp_linux_buffer_params_v1_create_immed(params, image_create_info.extent.width,
image_create_info.extent.height, fourcc, 0);
zwp_linux_buffer_params_v1_destroy(params);
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(image_data->buffer), m_buffer_queue);
auto res = wl_buffer_add_listener(image_data->buffer, &buffer_listener, this);
if (res < 0)
{
destroy_image(image);
return VK_ERROR_INITIALIZATION_FAILED;
}
return VK_SUCCESS;
}
VkResult swapchain::allocate_and_bind_swapchain_image(VkImageCreateInfo image_create_info, swapchain_image &image)
{
std::unique_lock<std::recursive_mutex> image_status_lock(m_image_status_mutex);
image.status = swapchain_image::FREE;
assert(image.data != nullptr);
auto image_data = static_cast<wayland_image_data *>(image.data);
TRY_LOG(allocate_image(image_create_info, image_data), "Failed to allocate image");
image_status_lock.unlock();
TRY_LOG(create_wl_buffer(image_create_info, image, image_data), "Failed to create wl_buffer");
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;
}
VkResult swapchain::create_swapchain_image(VkImageCreateInfo image_create_info, swapchain_image &image)
{
/* Create image_data */
auto image_data = m_allocator.create<wayland_image_data>(1, m_device, m_allocator);
if (image_data == nullptr)
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
image.data = image_data;
if (m_image_create_info.format == VK_FORMAT_UNDEFINED)
{
util::vector<wsialloc_format> importable_formats(
util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND));
util::vector<uint64_t> exportable_modifiers(util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND));
/* Query supported modifers. */
util::vector<VkDrmFormatModifierPropertiesEXT> drm_format_props(
util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND));
TRY_LOG_CALL(
get_surface_compatible_formats(image_create_info, importable_formats, exportable_modifiers, drm_format_props));
/* 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, true));
for (auto &prop : drm_format_props)
{
if (prop.drmFormatModifier == allocated_format.modifier)
{
image_data->external_mem.set_num_memories(prop.drmFormatModifierPlaneCount);
}
}
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_image_creation_parameters.m_allocated_format = allocated_format;
}
return m_device_data.disp.CreateImage(m_device, &m_image_create_info, get_allocation_callbacks(), &image.image);
}
void swapchain::present_image(const pending_present_request &pending_present)
{
int res;
wayland_image_data *image_data =
reinterpret_cast<wayland_image_data *>(m_swapchain_images[pending_present.image_index].data);
/* if a frame is already pending, wait for a hint to present again */
if (!m_wsi_surface->wait_next_frame_event())
{
set_error_state(VK_ERROR_SURFACE_LOST_KHR);
}
wl_surface_attach(m_surface, image_data->buffer, 0, 0);
auto present_sync_fd = image_data->present_fence.export_sync_fd();
if (!present_sync_fd.has_value())
{
WSI_LOG_ERROR("Failed to export present fence.");
set_error_state(VK_ERROR_SURFACE_LOST_KHR);
}
else if (present_sync_fd->is_valid())
{
zwp_linux_surface_synchronization_v1_set_acquire_fence(m_wsi_surface->get_surface_sync_interface(),
present_sync_fd->get());
}
/* TODO: work out damage */
wl_surface_damage(m_surface, 0, 0, INT32_MAX, INT32_MAX);
if (m_present_mode == VK_PRESENT_MODE_FIFO_KHR)
{
if (!m_wsi_surface->set_frame_callback())
{
set_error_state(VK_ERROR_SURFACE_LOST_KHR);
}
}
wl_surface_commit(m_surface);
res = wl_display_flush(m_display);
if (res < 0)
{
WSI_LOG_ERROR("error flushing the display");
/* Setting the swapchain as invalid */
set_error_state(VK_ERROR_SURFACE_LOST_KHR);
}
set_present_id(pending_present.present_id);
}
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<wayland_image_data *>(image.data);
if (image_data->buffer != nullptr)
{
wl_buffer_destroy(image_data->buffer);
}
m_allocator.destroy(1, image_data);
image.data = nullptr;
}
}
bool swapchain::free_image_found()
{
for (auto &img : m_swapchain_images)
{
if (img.status == swapchain_image::FREE)
{
return true;
}
}
return false;
}
VkResult swapchain::get_free_buffer(uint64_t *timeout)
{
int ms_timeout, res;
if (*timeout >= INT_MAX * 1000llu * 1000llu)
{
ms_timeout = INT_MAX;
}
else
{
ms_timeout = *timeout / 1000llu / 1000llu;
}
/* The current dispatch_queue implementation will return if any
* events are returned, even if no events are dispatched to the buffer
* queue. Therefore dispatch repeatedly until a buffer has been freed.
*/
do
{
res = dispatch_queue(m_display, m_buffer_queue, ms_timeout);
} while (!free_image_found() && res > 0);
if (res > 0)
{
*timeout = 0;
return VK_SUCCESS;
}
else if (res == 0)
{
if (*timeout == 0)
{
return VK_NOT_READY;
}
else
{
return VK_TIMEOUT;
}
}
else
{
return VK_ERROR_SURFACE_LOST_KHR;
}
}
VkResult swapchain::image_set_present_payload(swapchain_image &image, VkQueue queue,
const queue_submit_semaphores &semaphores, const void *submission_pnext)
{
auto image_data = reinterpret_cast<wayland_image_data *>(image.data);
return image_data->present_fence.set_payload(queue, semaphores, submission_pnext);
}
VkResult swapchain::image_wait_present(swapchain_image &, uint64_t)
{
/* With explicit sync in use there is no need to wait for the present sync before submiting the image to the
* compositor. */
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<wayland_image_data *>(swapchain_image.data);
return image_data->external_mem.bind_swapchain_image_memory(bind_image_mem_info->image);
}
} // namespace wayland
} // namespace wsi