all repos — mgba @ e9a2b2a57c4f478fb3600fe9d87e1b97804d73de

mGBA Game Boy Advance Emulator

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