src/gba/gba-thread.c (view raw)
1/* Copyright (c) 2013-2014 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "gba-thread.h"
7
8#include "arm.h"
9#include "gba.h"
10#include "gba-config.h"
11#include "gba-serialize.h"
12
13#include "debugger/debugger.h"
14
15#include "util/patch.h"
16#include "util/png-io.h"
17#include "util/vfs.h"
18
19#include "platform/commandline.h"
20
21#include <signal.h>
22
23static const float _defaultFPSTarget = 60.f;
24
25#ifdef USE_PTHREADS
26static pthread_key_t _contextKey;
27static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
28
29static void _createTLS(void) {
30 pthread_key_create(&_contextKey, 0);
31}
32#elif _WIN32
33static DWORD _contextKey;
34static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
35
36static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
37 UNUSED(once);
38 UNUSED(param);
39 UNUSED(context);
40 _contextKey = TlsAlloc();
41 return TRUE;
42}
43#endif
44
45#ifndef DISABLE_THREADING
46static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
47 MutexLock(&threadContext->stateMutex);
48 threadContext->state = newState;
49 if (broadcast) {
50 ConditionWake(&threadContext->stateCond);
51 }
52 MutexUnlock(&threadContext->stateMutex);
53}
54
55static void _waitOnInterrupt(struct GBAThread* threadContext) {
56 while (threadContext->state == THREAD_INTERRUPTED) {
57 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
58 }
59}
60
61static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
62 while (threadContext->state == oldState) {
63 MutexUnlock(&threadContext->stateMutex);
64
65 MutexLock(&threadContext->sync.videoFrameMutex);
66 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
67 MutexUnlock(&threadContext->sync.videoFrameMutex);
68
69 MutexLock(&threadContext->sync.audioBufferMutex);
70 ConditionWake(&threadContext->sync.audioRequiredCond);
71 MutexUnlock(&threadContext->sync.audioBufferMutex);
72
73 MutexLock(&threadContext->stateMutex);
74 ConditionWake(&threadContext->stateCond);
75 }
76}
77
78static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
79 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
80 threadContext->debugger->state = DEBUGGER_EXITING;
81 }
82 threadContext->state = THREAD_PAUSING;
83 if (!onThread) {
84 _waitUntilNotState(threadContext, THREAD_PAUSING);
85 }
86}
87#endif
88
89static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
90 // Make sure the video thread can process events while the GBA thread is paused
91 MutexLock(&sync->videoFrameMutex);
92 if (frameOn != sync->videoFrameOn) {
93 sync->videoFrameOn = frameOn;
94 ConditionWake(&sync->videoFrameAvailableCond);
95 }
96 MutexUnlock(&sync->videoFrameMutex);
97}
98
99#ifndef DISABLE_THREADING
100static THREAD_ENTRY _GBAThreadRun(void* context) {
101#ifdef USE_PTHREADS
102 pthread_once(&_contextOnce, _createTLS);
103#else
104 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
105#endif
106
107 struct GBA gba;
108 struct ARMCore cpu;
109 struct Patch patch;
110 struct GBAThread* threadContext = context;
111 struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
112 int numComponents = GBA_COMPONENT_MAX;
113
114#if !defined(_WIN32) && defined(USE_PTHREADS)
115 sigset_t signals;
116 sigemptyset(&signals);
117 pthread_sigmask(SIG_SETMASK, &signals, 0);
118#endif
119
120 GBACreate(&gba);
121 ARMSetComponents(&cpu, &gba.d, numComponents, components);
122 ARMInit(&cpu);
123 gba.sync = &threadContext->sync;
124 threadContext->gba = &gba;
125 gba.logLevel = threadContext->logLevel;
126#ifdef USE_PTHREADS
127 pthread_setspecific(_contextKey, threadContext);
128#else
129 TlsSetValue(_contextKey, threadContext);
130#endif
131
132 if (threadContext->audioBuffers) {
133 GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
134 } else {
135 threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
136 }
137
138 if (threadContext->renderer) {
139 GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
140 }
141
142 if (threadContext->rom) {
143 GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
144 if (threadContext->bios && GBAIsBIOS(threadContext->bios)) {
145 GBALoadBIOS(&gba, threadContext->bios);
146 }
147
148 if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
149 GBAApplyPatch(&gba, &patch);
150 }
151 }
152
153 ARMReset(&cpu);
154 if (threadContext->skipBios) {
155 GBASkipBIOS(&cpu);
156 }
157
158 if (threadContext->debugger) {
159 threadContext->debugger->log = GBADebuggerLogShim;
160 GBAAttachDebugger(&gba, threadContext->debugger);
161 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
162 }
163
164 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
165
166 gba.keySource = &threadContext->activeKeys;
167
168 if (threadContext->startCallback) {
169 threadContext->startCallback(threadContext);
170 }
171
172 _changeState(threadContext, THREAD_RUNNING, true);
173
174 while (threadContext->state < THREAD_EXITING) {
175 if (threadContext->debugger) {
176 struct ARMDebugger* debugger = threadContext->debugger;
177 ARMDebuggerRun(debugger);
178 if (debugger->state == DEBUGGER_SHUTDOWN) {
179 _changeState(threadContext, THREAD_EXITING, false);
180 }
181 } else {
182 while (threadContext->state == THREAD_RUNNING) {
183 ARMRunLoop(&cpu);
184 }
185 }
186
187 int resetScheduled = 0;
188 MutexLock(&threadContext->stateMutex);
189 while (threadContext->state > THREAD_RUNNING && threadContext->state < THREAD_EXITING) {
190 if (threadContext->state == THREAD_PAUSING) {
191 threadContext->state = THREAD_PAUSED;
192 ConditionWake(&threadContext->stateCond);
193 }
194 if (threadContext->state == THREAD_INTERRUPTING) {
195 threadContext->state = THREAD_INTERRUPTED;
196 ConditionWake(&threadContext->stateCond);
197 }
198 if (threadContext->state == THREAD_RESETING) {
199 threadContext->state = THREAD_RUNNING;
200 resetScheduled = 1;
201 }
202 while (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_INTERRUPTED) {
203 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
204 }
205 }
206 MutexUnlock(&threadContext->stateMutex);
207 if (resetScheduled) {
208 ARMReset(&cpu);
209 if (threadContext->skipBios) {
210 GBASkipBIOS(&cpu);
211 }
212 }
213 }
214
215 while (threadContext->state != THREAD_SHUTDOWN) {
216 _changeState(threadContext, THREAD_SHUTDOWN, false);
217 }
218
219 if (threadContext->cleanCallback) {
220 threadContext->cleanCallback(threadContext);
221 }
222
223 threadContext->gba = 0;
224 ARMDeinit(&cpu);
225 GBADestroy(&gba);
226
227 threadContext->sync.videoFrameOn = false;
228 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
229 ConditionWake(&threadContext->sync.audioRequiredCond);
230
231 return 0;
232}
233
234void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
235 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
236 threadContext->frameskip = opts->frameskip;
237 threadContext->logLevel = opts->logLevel;
238 if (opts->rewindEnable) {
239 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
240 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
241 } else {
242 threadContext->rewindBufferCapacity = 0;
243 }
244 threadContext->skipBios = opts->skipBios;
245 threadContext->sync.audioWait = opts->audioSync;
246 threadContext->sync.videoFrameWait = opts->videoSync;
247
248 if (opts->fpsTarget) {
249 threadContext->fpsTarget = opts->fpsTarget;
250 }
251
252 if (opts->audioBuffers) {
253 threadContext->audioBuffers = opts->audioBuffers;
254 }
255}
256
257void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
258 if (args->dirmode) {
259 threadContext->gameDir = VDirOpen(args->fname);
260 threadContext->stateDir = threadContext->gameDir;
261 } else {
262 threadContext->rom = VFileOpen(args->fname, O_RDONLY);
263#if ENABLE_LIBZIP
264 threadContext->gameDir = VDirOpenZip(args->fname, 0);
265#endif
266 }
267 threadContext->fname = args->fname;
268 threadContext->patch = VFileOpen(args->patch, O_RDONLY);
269}
270
271bool GBAThreadStart(struct GBAThread* threadContext) {
272 // TODO: error check
273 threadContext->activeKeys = 0;
274 threadContext->state = THREAD_INITIALIZED;
275 threadContext->sync.videoFrameOn = true;
276 threadContext->sync.videoFrameSkip = 0;
277
278 threadContext->rewindBuffer = 0;
279 int newCapacity = threadContext->rewindBufferCapacity;
280 int newInterval = threadContext->rewindBufferInterval;
281 threadContext->rewindBufferCapacity = 0;
282 threadContext->rewindBufferInterval = 0;
283 GBARewindSettingsChanged(threadContext, newCapacity, newInterval);
284
285 if (!threadContext->fpsTarget) {
286 threadContext->fpsTarget = _defaultFPSTarget;
287 }
288
289 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
290 threadContext->rom->close(threadContext->rom);
291 threadContext->rom = 0;
292 }
293
294 if (threadContext->gameDir) {
295 threadContext->gameDir->rewind(threadContext->gameDir);
296 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
297 while (dirent) {
298 struct Patch patchTemp;
299 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
300 if (!vf) {
301 continue;
302 }
303 if (!threadContext->rom && GBAIsROM(vf)) {
304 threadContext->rom = vf;
305 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
306 threadContext->patch = vf;
307 } else {
308 vf->close(vf);
309 }
310 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
311 }
312
313 }
314
315 if (!threadContext->rom) {
316 threadContext->state = THREAD_SHUTDOWN;
317 return false;
318 }
319
320 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
321
322 MutexInit(&threadContext->stateMutex);
323 ConditionInit(&threadContext->stateCond);
324
325 MutexInit(&threadContext->sync.videoFrameMutex);
326 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
327 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
328 MutexInit(&threadContext->sync.audioBufferMutex);
329 ConditionInit(&threadContext->sync.audioRequiredCond);
330
331 threadContext->interruptDepth = 0;
332
333#ifndef _WIN32
334 sigset_t signals;
335 sigemptyset(&signals);
336 sigaddset(&signals, SIGINT);
337 sigaddset(&signals, SIGTRAP);
338 pthread_sigmask(SIG_BLOCK, &signals, 0);
339#endif
340
341 MutexLock(&threadContext->stateMutex);
342 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
343 while (threadContext->state < THREAD_RUNNING) {
344 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
345 }
346 MutexUnlock(&threadContext->stateMutex);
347
348 return true;
349}
350
351bool GBAThreadHasStarted(struct GBAThread* threadContext) {
352 bool hasStarted;
353 MutexLock(&threadContext->stateMutex);
354 hasStarted = threadContext->state > THREAD_INITIALIZED;
355 MutexUnlock(&threadContext->stateMutex);
356 return hasStarted;
357}
358
359void GBAThreadEnd(struct GBAThread* threadContext) {
360 MutexLock(&threadContext->stateMutex);
361 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
362 threadContext->debugger->state = DEBUGGER_EXITING;
363 }
364 threadContext->state = THREAD_EXITING;
365 if (threadContext->gba) {
366 threadContext->gba->cpu->halted = false;
367 }
368 ConditionWake(&threadContext->stateCond);
369 MutexUnlock(&threadContext->stateMutex);
370 MutexLock(&threadContext->sync.audioBufferMutex);
371 threadContext->sync.audioWait = 0;
372 ConditionWake(&threadContext->sync.audioRequiredCond);
373 MutexUnlock(&threadContext->sync.audioBufferMutex);
374
375 MutexLock(&threadContext->sync.videoFrameMutex);
376 threadContext->sync.videoFrameWait = false;
377 threadContext->sync.videoFrameOn = false;
378 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
379 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
380 MutexUnlock(&threadContext->sync.videoFrameMutex);
381}
382
383void GBAThreadReset(struct GBAThread* threadContext) {
384 MutexLock(&threadContext->stateMutex);
385 _waitOnInterrupt(threadContext);
386 threadContext->state = THREAD_RESETING;
387 ConditionWake(&threadContext->stateCond);
388 MutexUnlock(&threadContext->stateMutex);
389}
390
391void GBAThreadJoin(struct GBAThread* threadContext) {
392 ThreadJoin(threadContext->thread);
393
394 MutexDeinit(&threadContext->stateMutex);
395 ConditionDeinit(&threadContext->stateCond);
396
397 MutexDeinit(&threadContext->sync.videoFrameMutex);
398 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
399 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
400 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
401 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
402
403 ConditionWake(&threadContext->sync.audioRequiredCond);
404 ConditionDeinit(&threadContext->sync.audioRequiredCond);
405 MutexDeinit(&threadContext->sync.audioBufferMutex);
406
407 int i;
408 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
409 if (threadContext->rewindBuffer[i]) {
410 GBADeallocateState(threadContext->rewindBuffer[i]);
411 }
412 }
413 free(threadContext->rewindBuffer);
414
415 if (threadContext->rom) {
416 threadContext->rom->close(threadContext->rom);
417 threadContext->rom = 0;
418 }
419
420 if (threadContext->save) {
421 threadContext->save->close(threadContext->save);
422 threadContext->save = 0;
423 }
424
425 if (threadContext->bios) {
426 threadContext->bios->close(threadContext->bios);
427 threadContext->bios = 0;
428 }
429
430 if (threadContext->patch) {
431 threadContext->patch->close(threadContext->patch);
432 threadContext->patch = 0;
433 }
434
435 if (threadContext->gameDir) {
436 if (threadContext->stateDir == threadContext->gameDir) {
437 threadContext->stateDir = 0;
438 }
439 threadContext->gameDir->close(threadContext->gameDir);
440 threadContext->gameDir = 0;
441 }
442
443 if (threadContext->stateDir) {
444 threadContext->stateDir->close(threadContext->stateDir);
445 threadContext->stateDir = 0;
446 }
447}
448
449bool GBAThreadIsActive(struct GBAThread* threadContext) {
450 return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
451}
452
453void GBAThreadInterrupt(struct GBAThread* threadContext) {
454 MutexLock(&threadContext->stateMutex);
455 ++threadContext->interruptDepth;
456 if (threadContext->interruptDepth > 1 || !GBAThreadIsActive(threadContext)) {
457 MutexUnlock(&threadContext->stateMutex);
458 return;
459 }
460 threadContext->savedState = threadContext->state;
461 _waitOnInterrupt(threadContext);
462 threadContext->state = THREAD_INTERRUPTING;
463 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
464 threadContext->debugger->state = DEBUGGER_EXITING;
465 }
466 ConditionWake(&threadContext->stateCond);
467 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
468 MutexUnlock(&threadContext->stateMutex);
469}
470
471void GBAThreadContinue(struct GBAThread* threadContext) {
472 MutexLock(&threadContext->stateMutex);
473 --threadContext->interruptDepth;
474 if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
475 threadContext->state = threadContext->savedState;
476 ConditionWake(&threadContext->stateCond);
477 }
478 MutexUnlock(&threadContext->stateMutex);
479}
480
481void GBAThreadPause(struct GBAThread* threadContext) {
482 bool frameOn = true;
483 MutexLock(&threadContext->stateMutex);
484 _waitOnInterrupt(threadContext);
485 if (threadContext->state == THREAD_RUNNING) {
486 _pauseThread(threadContext, false);
487 frameOn = false;
488 }
489 MutexUnlock(&threadContext->stateMutex);
490
491 _changeVideoSync(&threadContext->sync, frameOn);
492}
493
494void GBAThreadUnpause(struct GBAThread* threadContext) {
495 MutexLock(&threadContext->stateMutex);
496 _waitOnInterrupt(threadContext);
497 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
498 threadContext->state = THREAD_RUNNING;
499 ConditionWake(&threadContext->stateCond);
500 }
501 MutexUnlock(&threadContext->stateMutex);
502
503 _changeVideoSync(&threadContext->sync, true);
504}
505
506bool GBAThreadIsPaused(struct GBAThread* threadContext) {
507 bool isPaused;
508 MutexLock(&threadContext->stateMutex);
509 _waitOnInterrupt(threadContext);
510 isPaused = threadContext->state == THREAD_PAUSED;
511 MutexUnlock(&threadContext->stateMutex);
512 return isPaused;
513}
514
515void GBAThreadTogglePause(struct GBAThread* threadContext) {
516 bool frameOn = true;
517 MutexLock(&threadContext->stateMutex);
518 _waitOnInterrupt(threadContext);
519 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
520 threadContext->state = THREAD_RUNNING;
521 ConditionWake(&threadContext->stateCond);
522 } else if (threadContext->state == THREAD_RUNNING) {
523 _pauseThread(threadContext, false);
524 frameOn = false;
525 }
526 MutexUnlock(&threadContext->stateMutex);
527
528 _changeVideoSync(&threadContext->sync, frameOn);
529}
530
531void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
532 bool frameOn = true;
533 MutexLock(&threadContext->stateMutex);
534 _waitOnInterrupt(threadContext);
535 if (threadContext->state == THREAD_RUNNING) {
536 _pauseThread(threadContext, true);
537 frameOn = false;
538 }
539 MutexUnlock(&threadContext->stateMutex);
540
541 _changeVideoSync(&threadContext->sync, frameOn);
542}
543
544#ifdef USE_PTHREADS
545struct GBAThread* GBAThreadGetContext(void) {
546 pthread_once(&_contextOnce, _createTLS);
547 return pthread_getspecific(_contextKey);
548}
549#elif _WIN32
550struct GBAThread* GBAThreadGetContext(void) {
551 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
552 return TlsGetValue(_contextKey);
553}
554#endif
555
556#ifdef USE_PNG
557void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
558 unsigned stride;
559 void* pixels = 0;
560 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
561 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
562 png_structp png = PNGWriteOpen(vf);
563 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
564 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
565 PNGWriteClose(png, info);
566 vf->close(vf);
567}
568#endif
569
570#else
571struct GBAThread* GBAThreadGetContext(void) {
572 return 0;
573}
574#endif
575
576void GBASyncPostFrame(struct GBASync* sync) {
577 if (!sync) {
578 return;
579 }
580
581 MutexLock(&sync->videoFrameMutex);
582 ++sync->videoFramePending;
583 --sync->videoFrameSkip;
584 if (sync->videoFrameSkip < 0) {
585 do {
586 ConditionWake(&sync->videoFrameAvailableCond);
587 if (sync->videoFrameWait) {
588 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
589 }
590 } while (sync->videoFrameWait && sync->videoFramePending);
591 }
592 MutexUnlock(&sync->videoFrameMutex);
593
594 struct GBAThread* thread = GBAThreadGetContext();
595 if (!thread) {
596 return;
597 }
598
599 if (thread->rewindBuffer) {
600 --thread->rewindBufferNext;
601 if (thread->rewindBufferNext <= 0) {
602 thread->rewindBufferNext = thread->rewindBufferInterval;
603 GBARecordFrame(thread);
604 }
605 }
606 if (thread->stream) {
607 thread->stream->postVideoFrame(thread->stream, thread->renderer);
608 }
609 if (thread->frameCallback) {
610 thread->frameCallback(thread);
611 }
612}
613
614bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
615 if (!sync) {
616 return true;
617 }
618
619 MutexLock(&sync->videoFrameMutex);
620 ConditionWake(&sync->videoFrameRequiredCond);
621 if (!sync->videoFrameOn && !sync->videoFramePending) {
622 return false;
623 }
624 if (sync->videoFrameOn) {
625 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
626 }
627 sync->videoFramePending = 0;
628 sync->videoFrameSkip = frameskip;
629 return true;
630}
631
632void GBASyncWaitFrameEnd(struct GBASync* sync) {
633 if (!sync) {
634 return;
635 }
636
637 MutexUnlock(&sync->videoFrameMutex);
638}
639
640bool GBASyncDrawingFrame(struct GBASync* sync) {
641 if (!sync) {
642 return true;
643 }
644
645 return sync->videoFrameSkip <= 0;
646}
647
648void GBASyncSuspendDrawing(struct GBASync* sync) {
649 if (!sync) {
650 return;
651 }
652
653 _changeVideoSync(sync, false);
654}
655
656void GBASyncResumeDrawing(struct GBASync* sync) {
657 if (!sync) {
658 return;
659 }
660
661 _changeVideoSync(sync, true);
662}
663
664void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
665 if (!sync) {
666 return;
667 }
668
669 if (sync->audioWait && wait) {
670 // TODO loop properly in event of spurious wakeups
671 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
672 }
673 MutexUnlock(&sync->audioBufferMutex);
674}
675
676void GBASyncLockAudio(struct GBASync* sync) {
677 if (!sync) {
678 return;
679 }
680
681 MutexLock(&sync->audioBufferMutex);
682}
683
684void GBASyncUnlockAudio(struct GBASync* sync) {
685 if (!sync) {
686 return;
687 }
688
689 MutexUnlock(&sync->audioBufferMutex);
690}
691
692void GBASyncConsumeAudio(struct GBASync* sync) {
693 if (!sync) {
694 return;
695 }
696
697 ConditionWake(&sync->audioRequiredCond);
698 MutexUnlock(&sync->audioBufferMutex);
699}