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