/* * Copyright (c) 2024-2025 Arm Limited. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * @file swapchain.cpp * * @brief Contains the class implementation for a display swapchain. */ #include #include #include #include #include #include #include #include "swapchain.hpp" #include "present_wait_display.hpp" #include namespace wsi { namespace display { swapchain::swapchain(layer::device_private_data &dev_data, const VkAllocationCallbacks *pAllocator, surface &wsi_surface) : wsi::swapchain_base(dev_data, pAllocator) , m_wsi_allocator(nullptr) , m_display_mode(wsi_surface.get_display_mode()) , m_image_creation_parameters({}, m_allocator, {}, {}) { m_image_create_info.format = VK_FORMAT_UNDEFINED; } swapchain::~swapchain() { /* Call the base class teardown */ teardown(); } 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::add_required_extensions(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info) { auto compression_control = wsi_ext_image_compression_control::create(device, swapchain_create_info); if (compression_control) { if (!add_swapchain_extension(m_allocator.make_unique(*compression_control))) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } if (m_device_data.is_present_id_enabled() || (swapchain_create_info->flags & VK_SWAPCHAIN_CREATE_PRESENT_ID_2_BIT_KHR)) { if (!add_swapchain_extension(m_allocator.make_unique())) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } if (m_device_data.should_layer_handle_frame_boundary_events()) { if (!add_swapchain_extension(m_allocator.make_unique(m_device_data))) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } bool present_wait2; constexpr VkSwapchainCreateFlagsKHR present_wait2_mask = (VK_SWAPCHAIN_CREATE_PRESENT_ID_2_BIT_KHR | VK_SWAPCHAIN_CREATE_PRESENT_WAIT_2_BIT_KHR); present_wait2 = (swapchain_create_info->flags & present_wait2_mask) == present_wait2_mask; if (m_device_data.is_present_wait_enabled() || present_wait2) { if (!add_swapchain_extension(m_allocator.make_unique( *get_swapchain_extension(true), present_wait2))) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } return VK_SUCCESS; } VkResult swapchain::init_platform(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info, bool &use_presentation_thread) { UNUSED(device); UNUSED(swapchain_create_info); UNUSED(use_presentation_thread); auto wsi_allocator = swapchain_wsialloc_allocator::create(); if (!wsi_allocator.has_value()) { return VK_ERROR_INITIALIZATION_FAILED; } m_wsi_allocator = m_allocator.make_unique(std::move(*wsi_allocator)); if (m_wsi_allocator == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } if (is_mutable_format_enabled()) { WSI_LOG_ERROR("Mutable format swapchain is not supported for display backend"); 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) { UNUSED(device); const wsi::swapchain_image &swapchain_image = m_swapchain_images[bind_sc_info->imageIndex]; auto image_data = reinterpret_cast(swapchain_image.data); return image_data->external_mem.bind_swapchain_image_memory(bind_image_mem_info->image); } VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info, util::vector &importable_formats, util::vector &exportable_modifers, util::vector &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"); 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) { util::drm::drm_format_pair drm_format{ util::drm::vk_to_drm_format(info.format), prop.drmFormatModifier }; if (!display->is_format_supported(drm_format)) { 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; VkImageCompressionControlEXT compression_control = {}; if (m_device_data.is_swapchain_compression_control_enabled()) { auto *ext = get_swapchain_extension(); /* For Image compression control, additional requirements to be satisfied such as existence of VkImageCompressionControlEXT in swaphain_create_info for the ext to be added to the list. so we check whether we got a valid pointer and proceed if yes. */ if (ext) { compression_control = ext->get_compression_control_properties(); compression_control.pNext = image_info.pNext; image_info.pNext = &compression_control; } } result = m_device_data.instance_data.disp.GetPhysicalDeviceImageFormatProperties2KHR( m_device_data.physical_device, &image_info, &format_props); } if (result != VK_SUCCESS) { continue; } if (format_props.imageFormatProperties.maxExtent.width < info.extent.width || format_props.imageFormatProperties.maxExtent.height < info.extent.height || format_props.imageFormatProperties.maxExtent.depth < info.extent.depth) { continue; } if (format_props.imageFormatProperties.maxMipLevels < info.mipLevels || format_props.imageFormatProperties.maxArrayLayers < info.arrayLayers) { continue; } if ((format_props.imageFormatProperties.sampleCounts & info.samples) != info.samples) { continue; } if (external_props.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT_KHR) { if (!exportable_modifers.try_push_back(drm_format.modifier)) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } if (external_props.externalMemoryProperties.externalMemoryFeatures & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT_KHR) { uint64_t flags = (prop.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_DISJOINT_BIT) ? 0 : WSIALLOC_FORMAT_NON_DISJOINT; wsialloc_format import_format{ drm_format.fourcc, drm_format.modifier, flags }; if (!importable_formats.try_push_back(import_format)) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } } return VK_SUCCESS; } VkResult swapchain::allocate_wsialloc(VkImageCreateInfo &image_create_info, display_image_data *image_data, util::vector &importable_formats, wsialloc_format *allocated_format, bool avoid_allocation) { bool enable_fixed_rate = false; if (m_device_data.is_swapchain_compression_control_enabled()) { auto *ext = get_swapchain_extension(); /* For Image compression control, additional requirements to be satisfied such as existence of VkImageCompressionControlEXT in swaphain_create_info for the ext to be added to the list. so we check whether we got a valid pointer and proceed if yes. */ if (ext) { if (ext->get_bitmask_for_image_compression_flags() & VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT) { enable_fixed_rate = true; } } } allocation_params params = { (image_create_info.flags & VK_IMAGE_CREATE_PROTECTED_BIT) != 0, image_create_info.extent, importable_formats, enable_fixed_rate, avoid_allocation }; wsialloc_allocate_result alloc_result = { {}, { 0 }, { 0 }, { -1 }, false }; TRY(m_wsi_allocator->allocate(params, &alloc_result)); *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; } VkResult swapchain::allocate_image(display_image_data *image_data) { util::vector importable_formats(util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); auto &m_allocated_format = m_image_creation_parameters.m_allocated_format; if (!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_framebuffer(const VkImageCreateInfo &image_create_info, display_image_data *image_data) { VkResult ret_code = VK_SUCCESS; std::array strides{ 0, 0, 0, 0 }; std::array modifiers{ 0, 0, 0, 0 }; const util::drm::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()) { return VK_ERROR_INITIALIZATION_FAILED; } drm_gem_handle_array buffer_handles{ display->get_drm_fd() }; const auto &buffer_fds = image_data->external_mem.get_buffer_fds(); for (uint32_t plane = 0; plane < image_data->external_mem.get_num_planes(); plane++) { assert(image_data->external_mem.get_strides()[plane] > 0); strides[plane] = image_data->external_mem.get_strides()[plane]; 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)); return VK_ERROR_INITIALIZATION_FAILED; } } 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) { WSI_LOG_ERROR("Failed to create framebuffer: %s", strerror(errno)); return VK_ERROR_INITIALIZATION_FAILED; } return ret_code; } VkResult swapchain::allocate_and_bind_swapchain_image(VkImageCreateInfo image_create_info, swapchain_image &image) { util::unique_lock image_status_lock(m_image_status_mutex); if (!image_status_lock) { WSI_LOG_ERROR("Failed to acquire image status lock in allocate_and_bind_swapchain_image."); return VK_ERROR_INITIALIZATION_FAILED; } image.status = swapchain_image::FREE; assert(image.data != nullptr); auto image_data = static_cast(image.data); TRY_LOG(allocate_image(image_data), "Failed to allocate image"); image_status_lock.unlock(); TRY_LOG(create_framebuffer(image_create_info, 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; } VkResult swapchain::create_swapchain_image(VkImageCreateInfo image_create_info, swapchain_image &image) { /* Create image_data */ auto image_data = m_allocator.create(1, m_device, m_allocator); if (image_data == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } image.data = image_data; if (m_image_creation_parameters.m_allocated_format.fourcc == DRM_FORMAT_INVALID) { util::vector importable_formats( util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); util::vector exportable_modifiers(util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND)); /* Query supported modifers. */ util::vector 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 = { .fourcc = 0, .modifier = 0, .flags = 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); } } 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 drm_res = 0; display_image_data *image_data = reinterpret_cast(m_swapchain_images[pending_present.image_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 { int result = FD_ISSET(display->get_drm_fd(), &fds); assert(result > 0); UNUSED(result); 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_present.image_index].status = swapchain_image::PRESENTED; auto *ext = get_swapchain_extension(); if (ext != nullptr) { ext->mark_delivered(pending_present.present_id); } /* 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 queue_submit_semaphores &semaphores, const void *submission_pnext) { auto image_data = reinterpret_cast(image.data); return image_data->present_fence.set_payload(queue, semaphores, submission_pnext); } VkResult swapchain::image_wait_present(swapchain_image &image, uint64_t timeout) { auto data = reinterpret_cast(image.data); return data->present_fence.wait_payload(timeout); } void swapchain::destroy_image(swapchain_image &image) { util::unique_lock image_status_lock(m_image_status_mutex); if (!image_status_lock) { WSI_LOG_ERROR("Failed to acquire image status lock in destroy_image."); abort(); } if (image.status != swapchain_image::INVALID) { if (image.image != VK_NULL_HANDLE) { m_device_data.disp.DestroyImage(m_device, image.image, get_allocation_callbacks()); image.image = VK_NULL_HANDLE; } image.status = swapchain_image::INVALID; } image_status_lock.unlock(); if (image.data != nullptr) { auto image_data = reinterpret_cast(image.data); auto &display = drm_display::get_display(); if (!display.has_value()) { return; } if (image_data->fb_id != std::numeric_limits::max()) { int result = drmModeRmFB(display->get_drm_fd(), image_data->fb_id); assert(result == 0); UNUSED(result); } m_allocator.destroy(1, image_data); image.data = nullptr; } } VkResult swapchain::get_required_image_creator_extensions( const VkSwapchainCreateInfoKHR &swapchain_create_info, util::vector> *extensions) { auto compression_control = swapchain_image_create_compression_control::create( m_device_data.is_swapchain_compression_control_enabled(), swapchain_create_info); auto &display = drm_display::get_display(); if (!display.has_value()) { WSI_LOG_ERROR("DRM display not available."); return VK_ERROR_OUT_OF_HOST_MEMORY; } if (!extensions->try_push_back(m_allocator.make_unique( compression_control, m_wsi_allocator.get(), display->get_supported_formats(), m_device_data.physical_device, m_allocator))) { return VK_ERROR_OUT_OF_HOST_MEMORY; } return VK_SUCCESS; } } /* namespace display */ } /* namespace wsi*/