-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Expand file tree
/
Copy pathaudioworklet_emscripten_locks.c
More file actions
231 lines (207 loc) · 7 KB
/
audioworklet_emscripten_locks.c
File metadata and controls
231 lines (207 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#include <emscripten/wasm_worker.h>
#include <emscripten/webaudio.h>
#include <assert.h>
// Tests that these audio worklet compatible functions work, details in comments below:
//
// - _emscripten_thread_supports_atomics_wait()
// - emscripten_lock_init()
// - emscripten_lock_busyspin_wait_acquire()
// - emscripten_lock_release()
// - emscripten_get_now() in AW
// This needs to be big enough for a stereo output (1024 with a 128 frame) + working stack
#define AUDIO_STACK_SIZE 2048
// Define DISABLE_LOCKS to run the test without locking, which should statistically always fail
//#define DISABLE_LOCKS
// Number of times mainLoop() calculations get called
#define MAINLOOP_CALCS 10000
// Number of times MAINLOOP_CALCS are performed
#define MAINLOOP_RUNS 200
// Number of times process() calculations get called (called 3.75x more than mainLoop)
#define PROCESS_CALCS 2667
// Number of times PROCESS_CALCS are performed (3.75x more than mainLoop)
#define PROCESS_RUNS 750
// Internal, found in 'system/lib/pthread/threading_internal.h' (and requires building with -pthread)
int _emscripten_thread_supports_atomics_wait(void);
typedef enum {
// The test hasn't yet started
TEST_NOT_STARTED,
// Worklet ready and running the test
TEST_RUNNING,
// Main thread is finished, wait on worklet
TEST_DONE_MAIN,
// Test finished
TEST_DONE
} Test;
// Global audio context
EMSCRIPTEN_WEBAUDIO_T context;
// Lock used in all the tests
emscripten_lock_t testLock = EMSCRIPTEN_LOCK_T_STATIC_INITIALIZER;
// Which test is running (sometimes in the worklet, sometimes in the main thread)
_Atomic Test whichTest = TEST_NOT_STARTED;
// Time at which the test starts taken in main()
double startTime = 0;
// Counter for main, accessed only by main
int howManyMain = 0;
// Counter for the audio worklet, accessed only by the AW
int howManyProc = 0;
// Our dummy container
typedef struct {
uint32_t val0;
uint32_t val1;
uint32_t val2;
} Dummy;
// Container used to run the test
Dummy testData;
// Container to hold the expected value
Dummy trueData;
// Start values
void initDummy(Dummy* dummy) {
dummy->val0 = 4;
dummy->val1 = 1;
dummy->val2 = 2;
}
void printDummy(Dummy* dummy) {
emscripten_outf("Values: 0x%08X, 0x%08X, 0x%08X", dummy->val0, dummy->val1, dummy->val2);
}
// Run a simple calculation that will only be stable *if* all values are atomically updated
// (Currently approx. 200'000x from each thread)
void runCalcs(Dummy* dummy, int num) {
for (int n = 0; n < num; n++) {
#ifndef DISABLE_LOCKS
int have = emscripten_lock_busyspin_wait_acquire(&testLock, 1000);
assert(have);
#endif
dummy->val0 += dummy->val1 * dummy->val2;
dummy->val1 += dummy->val2 * dummy->val0;
dummy->val2 += dummy->val0 * dummy->val1;
dummy->val0 /= 4;
dummy->val1 /= 3;
dummy->val2 /= 2;
#ifndef DISABLE_LOCKS
emscripten_lock_release(&testLock);
#endif
}
}
void stopping() {
emscripten_out("Test done");
emscripten_destroy_audio_context(context);
emscripten_force_exit(0);
}
// AW callback
bool process(int numInputs, const AudioSampleFrame* inputs, int numOutputs, AudioSampleFrame* outputs, int numParams, const AudioParamFrame* params, void* data) {
assert(emscripten_current_thread_is_audio_worklet());
switch (whichTest) {
case TEST_NOT_STARTED:
whichTest = TEST_RUNNING;
break;
case TEST_RUNNING:
case TEST_DONE_MAIN:
if (howManyProc-- > 0) {
runCalcs((Dummy*) data, PROCESS_CALCS);
} else {
if (whichTest == TEST_DONE_MAIN) {
emscripten_outf("Worklet done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime));
// Both loops are finished
whichTest = TEST_DONE;
}
}
break;
case TEST_DONE:
return false;
}
return true;
}
// Main thread callback
bool mainLoop(double time, void* data) {
assert(!emscripten_current_thread_is_audio_worklet());
switch (whichTest) {
case TEST_NOT_STARTED:
break;
case TEST_RUNNING:
if (howManyMain-- > 0) {
runCalcs((Dummy*) data, MAINLOOP_CALCS);
} else {
emscripten_outf("Main thread done after %dms (expect: approx. 2s)", (int) (emscripten_get_now() - startTime));
// Done here, so signal to process()
whichTest = TEST_DONE_MAIN;
}
break;
case TEST_DONE_MAIN:
// Wait for process() to finish
break;
case TEST_DONE:
emscripten_out("Multi-thread results:");
printDummy((Dummy*) data);
assert(((Dummy*) data)->val0 == trueData.val0
&& ((Dummy*) data)->val1 == trueData.val1
&& ((Dummy*) data)->val2 == trueData.val2);
stopping();
return false;
}
return true;
}
EMSCRIPTEN_KEEPALIVE void startTest() {
startTime = emscripten_get_now();
if (emscripten_audio_context_state(context) != AUDIO_CONTEXT_STATE_RUNNING) {
emscripten_resume_audio_context_sync(context);
}
howManyMain = MAINLOOP_RUNS;
howManyProc = PROCESS_RUNS;
}
// HTML button to manually run the test
EM_JS(void, addButton, (), {
var button = document.createElement("button");
button.appendChild(document.createTextNode("Start Test"));
document.body.appendChild(button);
document.onclick = () => {
if (globalThis._startTest) {
_startTest();
}
};
});
// Audio processor created, now register the audio callback
void processorCreated(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) {
assert(success && "Audio worklet failed in processorCreated()");
emscripten_out("Audio worklet processor created");
// Single mono output
int outputChannelCounts[1] = { 1 };
EmscriptenAudioWorkletNodeCreateOptions opts = {
.numberOfOutputs = 1,
.outputChannelCounts = outputChannelCounts
};
EMSCRIPTEN_AUDIO_WORKLET_NODE_T worklet = emscripten_create_wasm_audio_worklet_node(ctx, "locks-test", &opts, &process, data);
emscripten_audio_node_connect(worklet, ctx, 0, 0);
}
// Worklet thread inited, now create the audio processor
void initialised(EMSCRIPTEN_WEBAUDIO_T ctx, bool success, void* data) {
assert(success && "Audio worklet failed in initialised()");
emscripten_out("Audio worklet initialised");
WebAudioWorkletProcessorCreateOptions opts = {
.name = "locks-test"
};
emscripten_create_wasm_audio_worklet_processor_async(ctx, &opts, &processorCreated, data);
}
int main() {
emscripten_lock_init(&testLock);
initDummy(&testData);
initDummy(&trueData);
// Canonical results, run in a single thread
for (int n = MAINLOOP_RUNS; n > 0; n--) {
runCalcs(&trueData, MAINLOOP_CALCS);
}
for (int n = PROCESS_RUNS; n > 0; n--) {
runCalcs(&trueData, PROCESS_CALCS);
}
emscripten_out("Single-thread results:");
printDummy(&trueData);
char* const workletStack = memalign(16, AUDIO_STACK_SIZE);
assert(workletStack);
// Audio processor callback setup
context = emscripten_create_audio_context(NULL);
assert(context);
emscripten_start_wasm_audio_worklet_thread_async(context, workletStack, AUDIO_STACK_SIZE, initialised, &testData);
emscripten_set_timeout_loop(mainLoop, 10, &testData);
addButton();
startTest(); // <-- May need a manual click to start
emscripten_exit_with_live_runtime();
}