Skip to content

Commit 97b7abc

Browse files
committed
add osx workgroups and global task scheduler
1 parent 70edf61 commit 97b7abc

23 files changed

Lines changed: 1137 additions & 122 deletions

framework/audio/common/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ target_sources(muse_audio_common PRIVATE
3333
iaudiothreadsecurer.h
3434
audiothreadsecurer.cpp
3535
audiothreadsecurer.h
36+
audioworkgroup.h
37+
audioworkgroup.cpp
38+
audiotaskscheduler.h
39+
iaudiotaskscheduler.h
40+
realtimethreadpool.h
41+
concurrentqueue.h
42+
lightweightsemaphore.h
3643
workmode.cpp
3744
workmode.h
3845
alignmentbuffer.h
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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_realtime_thread";
38+
public:
39+
void submitRealtimeTasksAndWait(const std::vector<Task>& tasks) override
40+
{
41+
std::lock_guard lock(m_threadPoolMutex);
42+
for (const auto& task : tasks) {
43+
IF_ASSERT_FAILED(m_threadPool->enqueue(task)) {
44+
task();
45+
}
46+
}
47+
m_threadPool->participateAndWait();
48+
}
49+
50+
void setAudioDriver(const IAudioDriverPtr& audioDriver)
51+
{
52+
if (!audioDriver) {
53+
return;
54+
}
55+
setWorkgroup(audioDriver->getAudioWorkGroup());
56+
audioDriver->currentWorkgroupChanged().onNotify(
57+
this, [this, audioDriverWeak = std::weak_ptr<IAudioDriver>(audioDriver)]() {
58+
if (auto audioDriver = audioDriverWeak.lock()) {
59+
setWorkgroup(audioDriver->getAudioWorkGroup());
60+
}
61+
});
62+
}
63+
64+
private:
65+
66+
void setWorkgroup(const AudioWorkGroup& workGroup)
67+
{
68+
std::lock_guard lock(m_threadPoolMutex);
69+
ensureThreadPoolSize(workGroup);
70+
m_threadPool->setAudioWorkgroup(workGroup);
71+
}
72+
73+
int getIdealThreadCount(AudioWorkGroup workGroup) const
74+
{
75+
int bestThreadHint = std::thread::hardware_concurrency();
76+
if (workGroup.getProvider() != nullptr) {
77+
bestThreadHint = workGroup.getMaxParallelThreadCount();
78+
}
79+
constexpr int hardwareToRealtimeRatio = 2; // This is a heuristic value. The optimal value may vary depending on the workload and system.
80+
return bestThreadHint > 0 ? static_cast<int>(bestThreadHint / hardwareToRealtimeRatio) : 1;
81+
}
82+
83+
void ensureThreadPoolSize(const AudioWorkGroup& currentWorkGroup)
84+
{
85+
auto idealWorkerCount = getIdealThreadCount(currentWorkGroup);
86+
std::lock_guard lock(m_threadPoolMutex);
87+
if (m_threadPool->getNumberOfWorkers() != idealWorkerCount) {
88+
m_threadPool = std::make_unique<RealtimeThreadPool>(threadpoolName, idealWorkerCount);
89+
AudioSanitizer::setMixerThreads(m_threadPool->threadIdSet());
90+
}
91+
}
92+
93+
std::unique_ptr<RealtimeThreadPool> m_threadPool{ std::make_unique<RealtimeThreadPool>(threadpoolName, getIdealThreadCount({})) };
94+
95+
std::recursive_mutex m_threadPoolMutex;
96+
};
97+
}
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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+
#include "audioworkgroup.h"
24+
25+
#include <memory>
26+
#include <utility>
27+
28+
#ifdef __APPLE__
29+
#include <os/object.h>
30+
#include <os/workgroup.h>
31+
#else
32+
#include <thread>
33+
#endif
34+
35+
namespace muse::audio {
36+
#ifdef __APPLE__
37+
class AudioWorkgroupTokenProvider
38+
{
39+
public:
40+
AudioWorkgroupTokenProvider(os_workgroup_t workgroup)
41+
: m_workgroup(workgroup)
42+
{
43+
if (!m_workgroup) {
44+
return;
45+
}
46+
47+
if (__builtin_available(macOS 11.0, *)) {
48+
os_retain(m_workgroup);
49+
auto status = os_workgroup_join(m_workgroup, &m_joinToken);
50+
if (status == 0) {
51+
return;
52+
}
53+
54+
os_release(m_workgroup);
55+
m_workgroup = nullptr;
56+
m_joinToken = {};
57+
return;
58+
}
59+
60+
m_workgroup = nullptr;
61+
}
62+
63+
bool isAttachedTo(os_workgroup_t wg) const { return m_workgroup == wg; }
64+
bool isValid() const { return m_workgroup != nullptr; }
65+
66+
AudioWorkgroupTokenProvider(const AudioWorkgroupTokenProvider& other) = delete;
67+
68+
AudioWorkgroupTokenProvider& operator=(const AudioWorkgroupTokenProvider&) = delete;
69+
70+
AudioWorkgroupTokenProvider(AudioWorkgroupTokenProvider&& other) noexcept
71+
: m_workgroup(other.m_workgroup), m_joinToken(other.m_joinToken)
72+
{
73+
other.m_workgroup = nullptr;
74+
other.m_joinToken = {};
75+
}
76+
77+
~AudioWorkgroupTokenProvider()
78+
{
79+
leave();
80+
}
81+
82+
private:
83+
void leave() noexcept
84+
{
85+
if (!m_workgroup) {
86+
return;
87+
}
88+
89+
if (__builtin_available(macOS 11.0, *)) {
90+
os_workgroup_leave(m_workgroup, &m_joinToken);
91+
}
92+
os_release(m_workgroup);
93+
m_workgroup = nullptr;
94+
m_joinToken = {};
95+
}
96+
97+
os_workgroup_t m_workgroup;
98+
os_workgroup_join_token_s m_joinToken;
99+
};
100+
101+
class AudioWorkgroupProvider
102+
{
103+
public:
104+
explicit AudioWorkgroupProvider(os_workgroup_t wg)
105+
: m_workgroup(wg)
106+
{
107+
os_retain(m_workgroup);
108+
}
109+
110+
~AudioWorkgroupProvider()
111+
{
112+
if (m_workgroup) {
113+
os_release(m_workgroup);
114+
}
115+
}
116+
117+
AudioWorkgroupProvider(const AudioWorkgroupProvider& other)
118+
: m_workgroup(other.m_workgroup)
119+
{
120+
os_retain(m_workgroup);
121+
}
122+
123+
AudioWorkgroupProvider& operator=(const AudioWorkgroupProvider& other)
124+
{
125+
if (this != &other) {
126+
os_retain(other.m_workgroup);
127+
if (m_workgroup) {
128+
os_release(m_workgroup);
129+
}
130+
m_workgroup = other.m_workgroup;
131+
}
132+
return *this;
133+
}
134+
135+
AudioWorkgroupProvider(AudioWorkgroupProvider&& other) noexcept
136+
: m_workgroup(other.m_workgroup)
137+
{
138+
other.m_workgroup = nullptr;
139+
}
140+
141+
bool join(AudioWorkgroupToken& tokenProvider) const
142+
{
143+
if (auto existingProvider = AudioWorkGroup::providerFor(tokenProvider);
144+
existingProvider != nullptr && existingProvider->isAttachedTo(m_workgroup)) {
145+
return true;
146+
}
147+
AudioWorkGroup::resetProviderFor(tokenProvider);
148+
AudioWorkgroupTokenProvider provider(m_workgroup);
149+
if (!provider.isValid()) {
150+
return false;
151+
}
152+
153+
AudioWorkGroup::setProviderFor(tokenProvider, [provider = std::move(provider)]() {
154+
return &provider;
155+
});
156+
return true;
157+
}
158+
159+
size_t getMaxParallelThreadCount() const
160+
{
161+
if (__builtin_available(macOS 11.0, *)) {
162+
return (size_t)os_workgroup_max_parallel_threads(m_workgroup, nullptr);
163+
}
164+
return 0;
165+
}
166+
167+
private:
168+
169+
os_workgroup_t m_workgroup;
170+
};
171+
172+
bool AudioWorkGroup::join(AudioWorkgroupToken& token)
173+
{
174+
auto provider = getProvider();
175+
if (!provider) {
176+
return false;
177+
}
178+
return provider->join(token);
179+
}
180+
181+
AudioWorkGroup makeAudioWorkgroup(void* opaqueHandle)
182+
{
183+
if (opaqueHandle == nullptr) {
184+
return {};
185+
}
186+
187+
os_workgroup_t handle = reinterpret_cast<os_workgroup_t>(opaqueHandle);
188+
189+
return AudioWorkGroup { std::make_unique<AudioWorkgroupProvider>(handle) };
190+
}
191+
192+
size_t AudioWorkGroup::getMaxParallelThreadCount() const
193+
{
194+
if (auto provider = getProvider(); provider) {
195+
return provider->getMaxParallelThreadCount();
196+
}
197+
return 0;
198+
}
199+
200+
#else
201+
202+
class AudioWorkgroupProvider
203+
{
204+
};
205+
206+
size_t AudioWorkGroup::getMaxParallelThreadCount() const
207+
{
208+
return std::thread::hardware_concurrency();
209+
}
210+
211+
bool AudioWorkGroup::join(AudioWorkgroupToken&)
212+
{
213+
return false;
214+
}
215+
216+
AudioWorkGroup makeAudioWorkgroup(void*)
217+
{
218+
return {};
219+
}
220+
221+
#endif
222+
223+
AudioWorkGroup::AudioWorkGroup() = default;
224+
225+
AudioWorkGroup::AudioWorkGroup(std::unique_ptr<AudioWorkgroupProvider> provider)
226+
: m_provider(std::move(provider)) {}
227+
228+
AudioWorkGroup::~AudioWorkGroup() = default;
229+
230+
AudioWorkGroup::AudioWorkGroup(const AudioWorkGroup& other)
231+
: m_provider(other.m_provider ? std::make_unique<AudioWorkgroupProvider>(*other.m_provider) : nullptr) {}
232+
233+
AudioWorkGroup::AudioWorkGroup(AudioWorkGroup&& other) noexcept = default;
234+
235+
AudioWorkGroup& AudioWorkGroup::operator=(const AudioWorkGroup& other)
236+
{
237+
if (this != &other) {
238+
m_provider = other.m_provider ? std::make_unique<AudioWorkgroupProvider>(*other.m_provider) : nullptr;
239+
}
240+
return *this;
241+
}
242+
243+
AudioWorkGroup& AudioWorkGroup::operator=(AudioWorkGroup&& other) noexcept = default;
244+
} // namespace muse::audio

0 commit comments

Comments
 (0)