Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
workgroup.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <cstdint>
8
9#if defined(__APPLE__)
10#include <os/workgroup.h>
11#include <pthread.h>
12#include <mach/mach.h>
13#include <mach/mach_time.h>
14#include <mach/thread_policy.h>
15#elif defined(__linux__)
16#include <pthread.h>
17#include <sched.h>
18#endif
19
20namespace pulp::audio {
21
40public:
41 AudioWorkgroup() = default;
43
46
47#if defined(__APPLE__)
50 void set_workgroup(os_workgroup_t workgroup) {
51 workgroup_ = workgroup;
52 }
53#endif
54
59 if (joined_) return true;
60
61#if defined(__APPLE__)
62 if (workgroup_) {
63 int result = os_workgroup_join(workgroup_, &token_);
64 if (result == 0) {
65 joined_ = true;
66 return true;
67 }
68 return false;
69 }
70
71 // Fallback: set real-time thread priority via Mach
72 joined_ = capture_and_set_realtime_priority();
73 return joined_;
74#else
75 // Other platforms: best-effort high priority
76 joined_ = set_high_priority();
77 return joined_;
78#endif
79 }
80
83 bool leave() {
84 if (!joined_)
85 return true;
86
87#if defined(__APPLE__)
88 if (workgroup_) {
89 os_workgroup_leave(workgroup_, &token_);
90 } else if (priority_thread_ != MACH_PORT_NULL) {
91 const auto result =
92 previous_constraint_is_default_
93 ? thread_policy_set(priority_thread_, THREAD_EXTENDED_POLICY,
94 reinterpret_cast<thread_policy_t>(&previous_extended_),
95 THREAD_EXTENDED_POLICY_COUNT)
96 : thread_policy_set(priority_thread_, THREAD_TIME_CONSTRAINT_POLICY,
97 reinterpret_cast<thread_policy_t>(&previous_constraint_),
98 THREAD_TIME_CONSTRAINT_POLICY_COUNT);
99 if (result != KERN_SUCCESS)
100 return false;
101 mach_port_deallocate(mach_task_self(), priority_thread_);
102 priority_thread_ = MACH_PORT_NULL;
103 }
104#endif
105 joined_ = false;
106 return true;
107 }
108
110 bool is_joined() const { return joined_; }
111
114 static bool set_realtime_priority() {
115#if defined(__APPLE__)
116 mach_timebase_info_data_t timebase;
117 mach_timebase_info(&timebase);
118
119 // Request real-time scheduling: ~5ms period, ~3ms computation
120 thread_time_constraint_policy_data_t policy;
121 policy.period = static_cast<uint32_t>(5000000ULL * timebase.denom / timebase.numer);
122 policy.computation = static_cast<uint32_t>(3000000ULL * timebase.denom / timebase.numer);
123 policy.constraint = static_cast<uint32_t>(5000000ULL * timebase.denom / timebase.numer);
124 policy.preemptible = TRUE;
125
126 const auto thread = mach_thread_self();
127 kern_return_t result = thread_policy_set(thread, THREAD_TIME_CONSTRAINT_POLICY,
128 reinterpret_cast<thread_policy_t>(&policy),
129 THREAD_TIME_CONSTRAINT_POLICY_COUNT);
130
131 mach_port_deallocate(mach_task_self(), thread);
132 return result == KERN_SUCCESS;
133#else
134 return set_high_priority();
135#endif
136 }
137
139 static bool set_high_priority() {
140#if defined(__APPLE__)
141 auto result = pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
142 return result == 0;
143#elif defined(__linux__)
144 struct sched_param param;
145 param.sched_priority = sched_get_priority_max(SCHED_FIFO);
146 // Don't use SCHED_FIFO as it requires root — use elevated normal priority
147 int policy = SCHED_OTHER;
148 param.sched_priority = 0; // max for SCHED_OTHER
149 pthread_setschedparam(pthread_self(), policy, &param);
150 return true;
151#elif defined(_WIN32)
152 // SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
153 return true; // Windows priority boost is not implemented here.
154#else
155 return false;
156#endif
157 }
158
159private:
160 bool joined_ = false;
161
162#if defined(__APPLE__)
163 bool capture_and_set_realtime_priority() {
164 priority_thread_ = mach_thread_self();
165 auto count = THREAD_TIME_CONSTRAINT_POLICY_COUNT;
166 previous_constraint_is_default_ = FALSE;
167 auto result = thread_policy_get(priority_thread_, THREAD_TIME_CONSTRAINT_POLICY,
168 reinterpret_cast<thread_policy_t>(&previous_constraint_),
169 &count, &previous_constraint_is_default_);
170 if (result == KERN_SUCCESS && previous_constraint_is_default_) {
171 count = THREAD_EXTENDED_POLICY_COUNT;
172 boolean_t get_default = FALSE;
173 result = thread_policy_get(priority_thread_, THREAD_EXTENDED_POLICY,
174 reinterpret_cast<thread_policy_t>(&previous_extended_),
175 &count, &get_default);
176 }
177 // Never change a policy we cannot subsequently restore.
178 if (result != KERN_SUCCESS || !set_realtime_priority()) {
179 mach_port_deallocate(mach_task_self(), priority_thread_);
180 priority_thread_ = MACH_PORT_NULL;
181 return false;
182 }
183 return true;
184 }
185
186 thread_t priority_thread_ = MACH_PORT_NULL;
187 thread_time_constraint_policy_data_t previous_constraint_{};
188 thread_extended_policy_data_t previous_extended_{};
189 boolean_t previous_constraint_is_default_ = FALSE;
190 os_workgroup_t workgroup_ = nullptr;
191 os_workgroup_join_token_s token_{};
192#endif
193};
194
195} // namespace pulp::audio
void set_workgroup(os_workgroup_t workgroup)
Set the workgroup from an AudioUnit or CoreAudio device.
Definition workgroup.hpp:50
static bool set_realtime_priority()
Set real-time thread priority without a workgroup (best-effort).
Definition workgroup.hpp:114
~AudioWorkgroup()
Definition workgroup.hpp:42
AudioWorkgroup(const AudioWorkgroup &)=delete
bool leave()
Leave on the joining thread and restore any scoped Mach scheduling change.
Definition workgroup.hpp:83
static bool set_high_priority()
Set high (but not real-time) thread priority.
Definition workgroup.hpp:139
bool is_joined() const
Check if the current thread has joined the workgroup.
Definition workgroup.hpp:110
bool join_from_audio_thread()
Join the audio workgroup from the current thread.
Definition workgroup.hpp:58
AudioWorkgroup & operator=(const AudioWorkgroup &)=delete
Definition analyzer_provider.hpp:12
Definition beat_division.hpp:11