/* * Copyright (c) 2017-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. */ #define VK_USE_PLATFORM_WAYLAND_KHR 1 #include "surface_properties.hpp" #include #include #include #include #include #include #include #include #include #include "swapchain.hpp" #include "util/drm/drm_utils.hpp" #include "util/format_modifiers.hpp" #include "util/helpers.hpp" #include "util/log.hpp" #include "util/macros.hpp" #include "wl_helpers.hpp" #include #include #include #include #include #include "present_timing_handler.hpp" #include "present_id_wayland.hpp" #include "present_wait_wayland.hpp" #include "wp_presentation_feedback.hpp" namespace wsi { namespace wayland { class wayland_image_data : public swapchain_image_data { public: wayland_image_data(wayland::wayland_owner buffer) : m_buffer(std::move(buffer)) { assert(m_buffer != nullptr); } ~wayland_image_data() override = default; wl_buffer *get_buffer() { return m_buffer.get(); } void remove_proxy() { wl_proxy_set_queue(reinterpret_cast(m_buffer.get()), nullptr); } private: wayland::wayland_owner m_buffer; }; 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_factory(m_allocator, m_device_data) { } swapchain::~swapchain() { teardown(); if (m_buffer_queue != nullptr) { for (auto &img : m_swapchain_images) { auto data = img.get_data(); if (data != nullptr) { data->remove_proxy(); } } wl_display_roundtrip_queue(m_display, m_buffer_queue); wl_event_queue_destroy(m_buffer_queue); } } VkResult swapchain::add_required_extensions(VkDevice device, const VkSwapchainCreateInfoKHR *swapchain_create_info) { UNUSED(device); if (m_device_data.is_present_id_enabled() || (swapchain_create_info->flags & VK_SWAPCHAIN_CREATE_PRESENT_ID_2_BIT_KHR)) { #if VULKAN_WSI_LAYER_EXPERIMENTAL if (!add_swapchain_extension(m_allocator.make_unique())) #else if (!add_swapchain_extension(m_allocator.make_unique())) #endif { return VK_ERROR_OUT_OF_HOST_MEMORY; } } if (m_device_data.is_swapchain_maintenance1_enabled()) { if (!add_swapchain_extension(m_allocator.make_unique(m_allocator))) { 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; } } 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; } } #if VULKAN_WSI_LAYER_EXPERIMENTAL bool swapchain_support_enabled = swapchain_create_info->flags & VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT; if (swapchain_support_enabled) { std::optional image_first_pixel_out_time_domain; if (m_wsi_surface->get_presentation_time_interface() != nullptr) { switch (m_wsi_surface->clockid()) { case CLOCK_MONOTONIC: image_first_pixel_out_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR; break; case CLOCK_MONOTONIC_RAW: image_first_pixel_out_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR; break; } } if (!add_swapchain_extension(wsi_ext_present_timing_wayland::create( m_device, m_allocator, image_first_pixel_out_time_domain, swapchain_create_info->minImageCount))) { return VK_ERROR_OUT_OF_HOST_MEMORY; } } #endif 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); 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; } /* * When VK_PRESENT_MODE_MAILBOX_KHR or VK_PRESENT_MODE_FIFO_LATEST_READY_EXT 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_FIFO_LATEST_READY_EXT) && (m_present_mode != VK_PRESENT_MODE_MAILBOX_KHR); auto present_wait = get_swapchain_extension(); if (present_wait) { present_wait->set_wayland_dispatcher(m_display, m_buffer_queue); } #if VULKAN_WSI_LAYER_EXPERIMENTAL auto *present_timing_ext = get_swapchain_extension(); if (present_timing_ext != nullptr) { present_timing_ext->init(m_display, m_buffer_queue); } #endif 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.value())); if (m_wsi_allocator == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } return init_image_factory(*swapchain_create_info); } VkResult swapchain::init_image_factory(const VkSwapchainCreateInfoKHR &swapchain_create_info) { std::variant> image_handle_creator_result = create_image_creator(swapchain_create_info); if (auto error = std::get_if(&image_handle_creator_result)) { return *error; } auto image_handle_creator = std::get>(std::move(image_handle_creator_result)); auto compression_control = image_create_compression_control::create(m_device, &swapchain_create_info); auto sc_img_create_ext_mem_result = swapchain_image_create_external_memory::create( image_handle_creator->get_image_create_info(), compression_control, *m_wsi_allocator, m_wsi_surface->get_formats(), m_device_data.physical_device, m_allocator); if (auto error = std::get_if(&sc_img_create_ext_mem_result)) { return *error; } auto sc_img_create_ext_mem = std::get>(std::move(sc_img_create_ext_mem_result)); auto external_image_create_info = sc_img_create_ext_mem->get_external_image_create_info(); TRY_LOG_CALL(image_handle_creator->add_extension(std::move(sc_img_create_ext_mem))); wsialloc_create_info_args wsialloc_args = { external_image_create_info.selected_format, external_image_create_info.flags, external_image_create_info.extent, external_image_create_info.explicit_compression }; auto backing_memory_creator = m_allocator.make_unique(m_device_data, *m_wsi_allocator, wsialloc_args); if (backing_memory_creator == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } m_image_factory.init(std::move(image_handle_creator), std::move(backing_memory_creator), true, false); return VK_SUCCESS; } VWL_CAPI_CALL(void) buffer_release(void *data, struct wl_buffer *wayl_buffer) VWL_API_POST { auto sc = reinterpret_cast(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 = m_swapchain_images[i].get_data(); if (data && data->get_buffer() == wayl_buffer) { #if VULKAN_WSI_LAYER_EXPERIMENTAL /* Some compositors might not deliver wp_presentation_feedback events if the images are pushed to compositor quick enough * in presentation modes that allow it (mailbox). If that happens, double check if these images were submitted for feedback * and handle it as a 'image discarded' event. */ auto *present_timing_ext = get_swapchain_extension(); if (present_timing_ext != nullptr) { present_timing_ext->mark_buffer_release(i); } auto *present_id_ext = get_swapchain_extension(); if (present_id_ext != nullptr) { present_id_ext->mark_buffer_release(i); } #endif 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 }; wayland_owner swapchain::create_wl_buffer(image_backing_memory_external &image_external_memory) { auto image_create_info = get_image_factory().get_image_handle_creator().get_image_create_info(); assert(image_create_info.extent.width <= INT32_MAX); assert(image_create_info.extent.height <= INT32_MAX); external_memory &ext_memory = image_external_memory.get_external_memory(); /* 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()); const uint64_t modifier = image_external_memory.get_image_create_info().selected_format.modifier; const auto modifier_hi = static_cast(modifier >> 32); const auto modifier_low = static_cast(modifier & 0xFFFFFFFF); for (uint32_t plane = 0; plane < ext_memory.get_num_planes(); plane++) { zwp_linux_buffer_params_v1_add(params, ext_memory.get_buffer_fds()[plane], plane, ext_memory.get_offsets()[plane], ext_memory.get_strides()[plane], modifier_hi, modifier_low); } const auto fourcc = util::drm::vk_to_drm_format(image_create_info.format); auto 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(buffer), m_buffer_queue); auto res = wl_buffer_add_listener(buffer, &buffer_listener, this); if (res < 0) { wl_buffer_destroy(buffer); return nullptr; } return wayland_owner(buffer); } VkResult swapchain::allocate_and_bind_swapchain_image(swapchain_image &image) { util::unique_lock image_status_lock(m_image_status_mutex); if (!image_status_lock) { WSI_LOG_ERROR("Failed to acquire mutex lock in allocate_and_bind_swapchain_image.\n"); return VK_ERROR_INITIALIZATION_FAILED; } auto &backing_memory = swapchain_image_factory::get_backing_memory_from_image(image); TRY_LOG_CALL(backing_memory.allocate()); wayland_owner buffer = nullptr; buffer = create_wl_buffer(backing_memory); if (buffer == nullptr) { WSI_LOG_ERROR("Failed to create wl_buffer"); return VK_ERROR_INITIALIZATION_FAILED; } TRY_LOG_CALL(backing_memory.import_and_bind(image.get_image())); auto image_data = m_allocator.make_unique(std::move(buffer)); if (image_data == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } image.set_data(std::move(image_data)); return VK_SUCCESS; } void swapchain::present_image(const pending_present_request &pending_present) { auto &image = m_swapchain_images[pending_present.image_index]; auto image_data = image.get_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->get_buffer(), 0, 0); auto present_sync_fd = static_cast(image.get_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); } } #if VULKAN_WSI_LAYER_EXPERIMENTAL auto *present_id_ext = get_swapchain_extension(); if (present_id_ext != nullptr) { if (m_wsi_surface->get_presentation_time_interface() != nullptr && pending_present.present_id) { wp_presentation *pres = m_wsi_surface->get_presentation_time_interface(); struct wp_presentation_feedback *feedback = wp_presentation_feedback(pres, m_wsi_surface->get_wl_surface()); wl_proxy_set_queue(reinterpret_cast(feedback), m_buffer_queue); presentation_feedback *feedback_obj = present_id_ext->insert_into_pending_present_feedback_list( pending_present.present_id, pending_present.image_index, feedback); if (feedback_obj == nullptr) { WSI_LOG_ERROR("Error adding to pending present feedback list"); set_error_state(VK_ERROR_SURFACE_LOST_KHR); return; } register_wp_presentation_feedback_listener(feedback, feedback_obj); } } auto *present_timing_ext = get_swapchain_extension(); if (present_timing_ext != nullptr) { if ((present_timing_ext->is_stage_pending_for_image_index(pending_present.image_index, VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT)) && (m_wsi_surface->get_presentation_time_interface() != nullptr)) { wp_presentation *pres = m_wsi_surface->get_presentation_time_interface(); struct wp_presentation_feedback *feedback = wp_presentation_feedback(pres, m_wsi_surface->get_wl_surface()); wl_proxy_set_queue(reinterpret_cast(feedback), m_buffer_queue); presentation_feedback *feedback_obj = present_timing_ext->insert_into_pending_present_feedback_list( pending_present.image_index, feedback, pending_present.present_id); if (feedback_obj == nullptr) { WSI_LOG_ERROR("Error adding to pending present feedback list"); set_error_state(VK_ERROR_SURFACE_LOST_KHR); return; } register_wp_presentation_feedback_listener(feedback, feedback_obj); } } #endif wl_surface_commit(m_surface); int 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); } #if VULKAN_WSI_LAYER_EXPERIMENTAL auto *ext = get_swapchain_extension(); if (ext != nullptr) { if (m_wsi_surface->get_presentation_time_interface() == nullptr) #else auto *ext = get_swapchain_extension(); if (ext != nullptr) #endif { ext->mark_delivered(pending_present.present_id); } #if VULKAN_WSI_LAYER_EXPERIMENTAL } #endif } bool swapchain::free_image_found() { for (auto &img : m_swapchain_images) { if (img.get_status() == swapchain_image::FREE) { return true; } } return false; } VkResult swapchain::get_free_buffer(uint64_t *timeout) { int ms_timeout, res; const uint64_t timeout_ns = *timeout; const uint64_t timeout_ms = timeout_ns / 1000000ULL; ms_timeout = timeout_ms > static_cast(INT_MAX) ? INT_MAX : static_cast(timeout_ms); /* 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; } } std::variant> swapchain::create_image_creator( const VkSwapchainCreateInfoKHR &swapchain_create_info) { auto image_handle_creator = m_allocator.make_unique(m_allocator, swapchain_create_info); if (image_handle_creator == nullptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } if (is_mutable_format_enabled()) { const auto *image_format_list = util::find_extension( VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, swapchain_create_info.pNext); auto mutable_format_uptr = swapchain_image_create_mutable_format::create_unique(image_format_list, m_allocator); if (!mutable_format_uptr) { return VK_ERROR_OUT_OF_HOST_MEMORY; } TRY_LOG_CALL(image_handle_creator->add_extension(std::move(mutable_format_uptr))); } return image_handle_creator; } swapchain_image_factory &swapchain::get_image_factory() { return m_image_factory; } } // namespace wayland } // namespace wsi