Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2026 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #pragma once
24 :
25 : #include "audioworkgroup.h"
26 : #include "realtimethreadpool.h"
27 : #include "iaudiodriver.h"
28 : #include "iaudiotaskscheduler.h"
29 : #include "audiosanitizer.h"
30 : #include "global/async/asyncable.h"
31 : #include "global/log.h"
32 : #include <mutex>
33 :
34 : namespace muse::audio {
35 : class AudioTaskScheduler : public IAudioTaskScheduler, public muse::async::Asyncable
36 : {
37 : constexpr static const char* threadpoolName = "audio_rt"; // The name must be no more than 12 characters.
38 : public:
39 0 : void submitRealtimeTasksAndWait(const std::vector<Task>& tasks) override
40 : {
41 0 : std::lock_guard lock(m_threadPoolMutex);
42 0 : for (const auto& task : tasks) {
43 0 : IF_ASSERT_FAILED(m_threadPool->enqueue(task)) {
44 0 : task();
45 : }
46 : }
47 0 : m_threadPool->participateAndWait();
48 0 : }
49 :
50 27 : void setAudioDriver(const IAudioDriverPtr& audioDriver)
51 : {
52 27 : if (!audioDriver) {
53 0 : return;
54 : }
55 27 : setWorkgroup(audioDriver->getAudioWorkGroup());
56 81 : audioDriver->currentWorkgroupChanged().onNotify(
57 54 : this, [this, audioDriverWeak = std::weak_ptr<IAudioDriver>(audioDriver)]() {
58 0 : if (auto audioDriver = audioDriverWeak.lock()) {
59 0 : setWorkgroup(audioDriver->getAudioWorkGroup());
60 0 : }
61 0 : });
62 : }
63 :
64 : private:
65 :
66 27 : void setWorkgroup(const AudioWorkGroup& workGroup)
67 : {
68 27 : std::lock_guard lock(m_threadPoolMutex);
69 27 : ensureThreadPoolSize(workGroup);
70 27 : m_threadPool->setAudioWorkgroup(workGroup);
71 27 : }
72 :
73 51 : int getIdealThreadCount(AudioWorkGroup workGroup) const
74 : {
75 51 : unsigned int bestThreadHint = std::thread::hardware_concurrency();
76 51 : if (workGroup.getProvider() != nullptr) {
77 0 : bestThreadHint = static_cast<unsigned int>(workGroup.getMaxParallelThreadCount());
78 : }
79 51 : constexpr unsigned int hardwareToRealtimeRatio = 2; // This is a heuristic value. The optimal value may vary depending on the workload and system.
80 51 : return bestThreadHint > 0 ? bestThreadHint / hardwareToRealtimeRatio : 1;
81 : }
82 :
83 27 : void ensureThreadPoolSize(const AudioWorkGroup& currentWorkGroup)
84 : {
85 27 : auto idealWorkerCount = getIdealThreadCount(currentWorkGroup);
86 27 : std::lock_guard lock(m_threadPoolMutex);
87 27 : if (m_threadPool->getNumberOfWorkers() != idealWorkerCount) {
88 0 : m_threadPool = std::make_unique<RealtimeThreadPool>(threadpoolName, idealWorkerCount);
89 0 : AudioSanitizer::setMixerThreads(m_threadPool->threadIdSet());
90 : }
91 27 : }
92 :
93 : std::unique_ptr<RealtimeThreadPool> m_threadPool{ std::make_unique<RealtimeThreadPool>(threadpoolName, getIdealThreadCount({})) };
94 :
95 : std::recursive_mutex m_threadPoolMutex;
96 : };
97 : }
|