diff options
-rw-r--r-- | src/audio_core/audio_out.cpp | 3 | ||||
-rw-r--r-- | src/audio_core/cubeb_sink.cpp | 2 | ||||
-rw-r--r-- | src/audio_core/cubeb_sink.h | 2 | ||||
-rw-r--r-- | src/audio_core/null_sink.h | 2 | ||||
-rw-r--r-- | src/audio_core/sink_details.cpp | 53 | ||||
-rw-r--r-- | src/audio_core/sink_details.h | 25 | ||||
-rw-r--r-- | src/common/thread_queue_list.h | 16 | ||||
-rw-r--r-- | src/core/hle/kernel/scheduler.cpp | 66 | ||||
-rw-r--r-- | src/core/hle/kernel/scheduler.h | 69 | ||||
-rw-r--r-- | src/core/hle/kernel/svc.cpp | 38 | ||||
-rw-r--r-- | src/core/hle/kernel/thread.h | 1 | ||||
-rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp | 4 | ||||
-rw-r--r-- | src/core/hle/service/service.cpp | 14 | ||||
-rw-r--r-- | src/core/hle/service/service.h | 8 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_audio.cpp | 7 |
15 files changed, 255 insertions, 55 deletions
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 0c8f5b18e..cbba17632 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) { StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name, Stream::ReleaseCallback&& release_callback) { if (!sink) { - const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id); - sink = sink_details.factory(Settings::values.audio_device_id); + sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id); } return std::make_shared<Stream>( diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index d31a1c844..097328901 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -107,7 +107,7 @@ private: static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); }; -CubebSink::CubebSink(std::string target_device_name) { +CubebSink::CubebSink(std::string_view target_device_name) { if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); return; diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 59cbf05e9..efb9d1634 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -15,7 +15,7 @@ namespace AudioCore { class CubebSink final : public Sink { public: - explicit CubebSink(std::string device_id); + explicit CubebSink(std::string_view device_id); ~CubebSink() override; SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index a78d78893..61a28d542 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -10,7 +10,7 @@ namespace AudioCore { class NullSink final : public Sink { public: - explicit NullSink(std::string){}; + explicit NullSink(std::string_view) {} ~NullSink() override = default; SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 67cf1f3b2..a848eb1c9 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -14,31 +14,68 @@ #include "common/logging/log.h" namespace AudioCore { +namespace { +struct SinkDetails { + using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); + using ListDevicesFn = std::vector<std::string> (*)(); -// g_sink_details is ordered in terms of desirability, with the best choice at the top. -const std::vector<SinkDetails> g_sink_details = { + /// Name for this sink. + const char* id; + /// A method to call to construct an instance of this type of sink. + FactoryFn factory; + /// A method to call to list available devices. + ListDevicesFn list_devices; +}; + +// sink_details is ordered in terms of desirability, with the best choice at the top. +constexpr SinkDetails sink_details[] = { #ifdef HAVE_CUBEB - SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices}, + SinkDetails{"cubeb", + [](std::string_view device_id) -> std::unique_ptr<Sink> { + return std::make_unique<CubebSink>(device_id); + }, + &ListCubebSinkDevices}, #endif - SinkDetails{"null", &std::make_unique<NullSink, std::string>, + SinkDetails{"null", + [](std::string_view device_id) -> std::unique_ptr<Sink> { + return std::make_unique<NullSink>(device_id); + }, [] { return std::vector<std::string>{"null"}; }}, }; const SinkDetails& GetSinkDetails(std::string_view sink_id) { auto iter = - std::find_if(g_sink_details.begin(), g_sink_details.end(), + std::find_if(std::begin(sink_details), std::end(sink_details), [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); - if (sink_id == "auto" || iter == g_sink_details.end()) { + if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); } // Auto-select. - // g_sink_details is ordered in terms of desirability, with the best choice at the front. - iter = g_sink_details.begin(); + // sink_details is ordered in terms of desirability, with the best choice at the front. + iter = std::begin(sink_details); } return *iter; } +} // Anonymous namespace + +std::vector<const char*> GetSinkIDs() { + std::vector<const char*> sink_ids(std::size(sink_details)); + + std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids), + [](const auto& sink) { return sink.id; }); + + return sink_ids; +} + +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { + return GetSinkDetails(sink_id).list_devices(); +} + +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { + return GetSinkDetails(sink_id).factory(device_id); +} } // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index 03534b187..bc8786270 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -4,34 +4,21 @@ #pragma once -#include <functional> -#include <memory> #include <string> #include <string_view> -#include <utility> #include <vector> namespace AudioCore { class Sink; -struct SinkDetails { - using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>; - using ListDevicesFn = std::function<std::vector<std::string>()>; +/// Retrieves the IDs for all available audio sinks. +std::vector<const char*> GetSinkIDs(); - SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_) - : id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {} +/// Gets the list of devices for a particular sink identified by the given ID. +std::vector<std::string> GetDeviceListForSink(std::string_view sink_id); - /// Name for this sink. - const char* id; - /// A method to call to construct an instance of this type of sink. - FactoryFn factory; - /// A method to call to list available devices. - ListDevicesFn list_devices; -}; - -extern const std::vector<SinkDetails> g_sink_details; - -const SinkDetails& GetSinkDetails(std::string_view sink_id); +/// Creates an audio sink identified by the given device ID. +std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id); } // namespace AudioCore diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index 133122c5f..e7594db68 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -49,6 +49,22 @@ struct ThreadQueueList { return T(); } + template <typename UnaryPredicate> + T get_first_filter(UnaryPredicate filter) const { + const Queue* cur = first; + while (cur != nullptr) { + if (!cur->data.empty()) { + for (const auto& item : cur->data) { + if (filter(item)) + return item; + } + } + cur = cur->next_nonempty; + } + + return T(); + } + T pop_first() { Queue* cur = first; while (cur != nullptr) { diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 5a5f4cef1..df4d6cf0a 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -9,6 +9,7 @@ #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/core_cpu.h" #include "core/core_timing.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -179,4 +180,69 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) { ready_queue.prepare(priority); } +Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const { + std::lock_guard<std::mutex> lock(scheduler_mutex); + + const u32 mask = 1U << core; + return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) { + return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority; + }); +} + +void Scheduler::YieldWithoutLoadBalancing(Thread* thread) { + ASSERT(thread != nullptr); + // Avoid yielding if the thread isn't even running. + ASSERT(thread->GetStatus() == ThreadStatus::Running); + + // Sanity check that the priority is valid + ASSERT(thread->GetPriority() < THREADPRIO_COUNT); + + // Yield this thread -- sleep for zero time and force reschedule to different thread + WaitCurrentThread_Sleep(); + GetCurrentThread()->WakeAfterDelay(0); +} + +void Scheduler::YieldWithLoadBalancing(Thread* thread) { + ASSERT(thread != nullptr); + const auto priority = thread->GetPriority(); + const auto core = static_cast<u32>(thread->GetProcessorID()); + + // Avoid yielding if the thread isn't even running. + ASSERT(thread->GetStatus() == ThreadStatus::Running); + + // Sanity check that the priority is valid + ASSERT(priority < THREADPRIO_COUNT); + + // Sleep for zero time to be able to force reschedule to different thread + WaitCurrentThread_Sleep(); + GetCurrentThread()->WakeAfterDelay(0); + + Thread* suggested_thread = nullptr; + + // Search through all of the cpu cores (except this one) for a suggested thread. + // Take the first non-nullptr one + for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) { + const auto res = + Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread( + core, priority); + + // If scheduler provides a suggested thread + if (res != nullptr) { + // And its better than the current suggested thread (or is the first valid one) + if (suggested_thread == nullptr || + suggested_thread->GetPriority() > res->GetPriority()) { + suggested_thread = res; + } + } + } + + // If a suggested thread was found, queue that for this core + if (suggested_thread != nullptr) + suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask()); +} + +void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) { + UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!"); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index c63032b7d..97ced4dfc 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -51,6 +51,75 @@ public: /// Sets the priority of a thread in the scheduler void SetThreadPriority(Thread* thread, u32 priority); + /// Gets the next suggested thread for load balancing + Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const; + + /** + * YieldWithoutLoadBalancing -- analogous to normal yield on a system + * Moves the thread to the end of the ready queue for its priority, and then reschedules the + * system to the new head of the queue. + * + * Example (Single Core -- but can be extrapolated to multi): + * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->) + * Currently Running: ThreadR + * + * ThreadR calls YieldWithoutLoadBalancing + * + * ThreadR is moved to the end of ready_queue[prio=0]: + * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->) + * Currently Running: Nothing + * + * System is rescheduled (ThreadA is popped off of queue): + * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->) + * Currently Running: ThreadA + * + * If the queue is empty at time of call, no yielding occurs. This does not cross between cores + * or priorities at all. + */ + void YieldWithoutLoadBalancing(Thread* thread); + + /** + * YieldWithLoadBalancing -- yield but with better selection of the new running thread + * Moves the current thread to the end of the ready queue for its priority, then selects a + * 'suggested thread' (a thread on a different core that could run on this core) from the + * scheduler, changes its core, and reschedules the current core to that thread. + * + * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were + * single core): + * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant + * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] + * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 + * + * ThreadQ calls YieldWithLoadBalancing + * + * ThreadQ is moved to the end of ready_queue[core=0][prio=0]: + * ready_queue[core=0][prio=0]: ThreadA, ThreadB + * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only] + * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1 + * + * A list of suggested threads for each core is compiled + * Suggested Threads: {ThreadC on Core 1} + * If this were quad core (as the switch is), there could be between 0 and 3 threads in this + * list. If there are more than one, the thread is selected by highest prio. + * + * ThreadC is core changed to Core 0: + * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ + * ready_queue[core=1][prio=0]: ThreadD + * Currently Running: None on Core 0 || ThreadP on Core 1 + * + * System is rescheduled (ThreadC is popped off of queue): + * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ + * ready_queue[core=1][prio=0]: ThreadD + * Currently Running: ThreadC on Core 0 || ThreadP on Core 1 + * + * If no suggested threads can be found this will behave just as normal yield. If there are + * multiple candidates for the suggested thread on a core, the highest prio is taken. + */ + void YieldWithLoadBalancing(Thread* thread); + + /// Currently unknown -- asserts as unimplemented on call + void YieldAndWaitForLoadBalancing(Thread* thread); + /// Returns a list of all threads managed by the scheduler const std::vector<SharedPtr<Thread>>& GetThreadList() const { return thread_list; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 5d36792ca..348a22904 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1208,18 +1208,38 @@ static void ExitThread() { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); - // Don't attempt to yield execution if there are no available threads to run, - // this way we avoid a useless reschedule to the idle thread. - if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads()) - return; + enum class SleepType : s64 { + YieldWithoutLoadBalancing = 0, + YieldWithLoadBalancing = -1, + YieldAndWaitForLoadBalancing = -2, + }; - // Sleep current thread and check for next thread to schedule - WaitCurrentThread_Sleep(); + if (nanoseconds <= 0) { + auto& scheduler{Core::System::GetInstance().CurrentScheduler()}; + switch (static_cast<SleepType>(nanoseconds)) { + case SleepType::YieldWithoutLoadBalancing: + scheduler.YieldWithoutLoadBalancing(GetCurrentThread()); + break; + case SleepType::YieldWithLoadBalancing: + scheduler.YieldWithLoadBalancing(GetCurrentThread()); + break; + case SleepType::YieldAndWaitForLoadBalancing: + scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread()); + break; + default: + UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); + } + } else { + // Sleep current thread and check for next thread to schedule + WaitCurrentThread_Sleep(); - // Create an event to wake the thread up after the specified nanosecond delay has passed - GetCurrentThread()->WakeAfterDelay(nanoseconds); + // Create an event to wake the thread up after the specified nanosecond delay has passed + GetCurrentThread()->WakeAfterDelay(nanoseconds); + } - Core::System::GetInstance().PrepareReschedule(); + // Reschedule all CPU cores + for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) + Core::System::GetInstance().CpuCore(i).PrepareReschedule(); } /// Wait process wide key atomic diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index d384d50db..77aec099a 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -26,6 +26,7 @@ enum ThreadPriority : u32 { THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps THREADPRIO_LOWEST = 63, ///< Lowest thread priority + THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities. }; enum ThreadProcessorId : s32 { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 3bfce0110..0a650f36c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -137,6 +137,10 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector< } static void PushGPUEntries(Tegra::CommandList&& entries) { + if (entries.empty()) { + return; + } + auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()}; dma_pusher.Push(std::move(entries)); dma_pusher.DispatchCalls(); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index d41df3732..d25b80ab0 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -97,29 +97,33 @@ ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_ses ServiceFrameworkBase::~ServiceFrameworkBase() = default; void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { - ASSERT(port == nullptr); - port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); + ASSERT(!port_installed); + + auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); port->SetHleHandler(shared_from_this()); + port_installed = true; } void ServiceFrameworkBase::InstallAsNamedPort() { - ASSERT(port == nullptr); + ASSERT(!port_installed); auto& kernel = Core::System::GetInstance().Kernel(); auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); server_port->SetHleHandler(shared_from_this()); kernel.AddNamedPort(service_name, std::move(client_port)); + port_installed = true; } Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { - ASSERT(port == nullptr); + ASSERT(!port_installed); auto& kernel = Core::System::GetInstance().Kernel(); auto [server_port, client_port] = Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); - port = MakeResult(std::move(server_port)).Unwrap(); + auto port = MakeResult(std::move(server_port)).Unwrap(); port->SetHleHandler(shared_from_this()); + port_installed = true; return client_port; } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 98483ecf1..029533628 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -96,11 +96,9 @@ private: /// Maximum number of concurrent sessions that this service can handle. u32 max_sessions; - /** - * Port where incoming connections will be received. Only created when InstallAsService() or - * InstallAsNamedPort() are called. - */ - Kernel::SharedPtr<Kernel::ServerPort> port; + /// Flag to store if a port was already create/installed to detect multiple install attempts, + /// which is not supported. + bool port_installed = false; /// Function used to safely up-cast pointers to the derived class before invoking a handler. InvokerFn* handler_invoker; diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index eb1da0f9e..5d9ccc6e8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent) ui->output_sink_combo_box->clear(); ui->output_sink_combo_box->addItem("auto"); - for (const auto& sink_detail : AudioCore::g_sink_details) { - ui->output_sink_combo_box->addItem(sink_detail.id); + for (const char* id : AudioCore::GetSinkIDs()) { + ui->output_sink_combo_box->addItem(id); } connect(ui->volume_slider, &QSlider::valueChanged, this, @@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) { ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); - for (const auto& device : device_list) { + for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { ui->audio_device_combo_box->addItem(QString::fromStdString(device)); } } |