#include "VKAsyncScheduler.h" #include "VKHelpers.h" #include "VKResourceManager.h" #include "Emu/IdManager.h" #include "Utilities/lockless.h" #include "Utilities/mutex.h" #include namespace vk { AsyncTaskScheduler::AsyncTaskScheduler([[maybe_unused]] vk_gpu_scheduler_mode mode) { if (g_cfg.video.renderer != video_renderer::vulkan || !g_cfg.video.vk.asynchronous_texture_streaming) { // Invalid renderer combination, do not proceed. This should never happen. // NOTE: If managed by fxo, this object may be created automatically on boot. rsx_log.notice("Vulkan async streaming is disabled. This thread will now exit."); return; } init_config_options(mode); } AsyncTaskScheduler::~AsyncTaskScheduler() { if (!m_async_command_queue.empty()) { // Driver resources should be destroyed before driver is detached or you get crashes. RAII won't save you here. rsx_log.error("Async task scheduler resources were not freed correctly!"); } } void AsyncTaskScheduler::init_config_options(vk_gpu_scheduler_mode mode) { std::lock_guard lock(m_config_mutex); if (std::exchange(m_options_initialized, true)) { // Nothing to do return; } m_use_host_scheduler = (mode == vk_gpu_scheduler_mode::safe) || g_cfg.video.strict_rendering_mode; rsx_log.notice("Asynchronous task scheduler is active running in %s mode", m_use_host_scheduler? "'Safe'" : "'Fast'"); } void AsyncTaskScheduler::delayed_init() { ensure(m_options_initialized); auto pdev = get_current_renderer(); m_command_pool.create(*const_cast(pdev), pdev->get_transfer_queue_family()); if (m_use_host_scheduler) { for (usz i = 0; i < events_pool_size; ++i) { auto sema = std::make_unique(*pdev); m_semaphore_pool.emplace_back(std::move(sema)); } return; } for (usz i = 0; i < events_pool_size; ++i) { auto ev = std::make_unique(*pdev, sync_domain::gpu); m_events_pool.emplace_back(std::move(ev)); } } void AsyncTaskScheduler::insert_sync_event() { ensure(m_current_cb); auto& sync_label = m_events_pool[m_next_event_id++ % events_pool_size]; sync_label->reset(); sync_label->signal(*m_current_cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0); m_sync_label = sync_label.get(); } command_buffer* AsyncTaskScheduler::get_current() { std::lock_guard lock(m_submit_mutex); m_sync_required = true; // 0. Anything still active? if (m_current_cb) { return m_current_cb; } // 1. Check if there is a 'next' entry if (m_async_command_queue.empty()) { delayed_init(); } else if (m_next_cb_index < m_async_command_queue.size()) { m_current_cb = &m_async_command_queue[m_next_cb_index]; } // 2. Create entry if (!m_current_cb) { if (m_next_cb_index == VK_MAX_ASYNC_COMPUTE_QUEUES) { m_next_cb_index = 0; m_current_cb = &m_async_command_queue[m_next_cb_index]; } else { m_async_command_queue.emplace_back(); m_current_cb = &m_async_command_queue.back(); m_current_cb->create(m_command_pool); } } m_next_cb_index++; return m_current_cb; } event* AsyncTaskScheduler::get_primary_sync_label() { ensure(!m_use_host_scheduler); if (m_sync_required) [[unlikely]] { std::lock_guard lock(m_submit_mutex); // For some reason this is inexplicably expensive. WTF! ensure(m_current_cb); insert_sync_event(); m_sync_required = false; } return std::exchange(m_sync_label, nullptr); } semaphore* AsyncTaskScheduler::get_sema() { if (m_semaphore_pool.empty()) { delayed_init(); ensure(!m_semaphore_pool.empty()); } const u32 sema_id = static_cast(m_next_semaphore_id++ % m_semaphore_pool.size()); return m_semaphore_pool[sema_id].get(); } void AsyncTaskScheduler::flush(queue_submit_t& submit_info, VkBool32 force_flush) { if (!m_current_cb) { return; } submit_info.queue = get_current_renderer()->get_transfer_queue(); std::lock_guard lock(m_submit_mutex); if (m_sync_required && !m_use_host_scheduler) { insert_sync_event(); } m_current_cb->end(); m_current_cb->submit(submit_info, force_flush); m_submit_count++; m_last_used_cb = m_current_cb; m_current_cb = nullptr; m_sync_required = false; } void AsyncTaskScheduler::destroy() { for (auto& cb : m_async_command_queue) { cb.destroy(); } m_async_command_queue.clear(); m_next_cb_index = 0; m_command_pool.destroy(); m_events_pool.clear(); m_semaphore_pool.clear(); } }