all repos — mgba @ eb4c41d6fc8ab1ed16e9812e4f54a1de93d6cc3c

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-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#ifdef USE_PTHREADS
128	pthread_setspecific(_contextKey, threadContext);
129#else
130	TlsSetValue(_contextKey, threadContext);
131#endif
132
133	if (threadContext->audioBuffers) {
134		GBAAudioResizeBuffer(&gba.audio, threadContext->audioBuffers);
135	} else {
136		threadContext->audioBuffers = GBA_AUDIO_SAMPLES;
137	}
138
139	if (threadContext->renderer) {
140		GBAVideoAssociateRenderer(&gba.video, threadContext->renderer);
141	}
142
143	if (threadContext->rom) {
144		GBALoadROM(&gba, threadContext->rom, threadContext->save, threadContext->fname);
145
146		struct GBACartridgeOverride override;
147		const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
148		memcpy(override.id, &cart->id, sizeof(override.id));
149		if (GBAOverrideFind(threadContext->overrides, &override)) {
150			GBAOverrideApply(&gba, &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);
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
266void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
267	if (args->dirmode) {
268		threadContext->gameDir = VDirOpen(args->fname);
269		threadContext->stateDir = threadContext->gameDir;
270	} else {
271		threadContext->rom = VFileOpen(args->fname, O_RDONLY);
272		threadContext->gameDir = 0;
273#if ENABLE_LIBZIP
274		if (!threadContext->gameDir) {
275			threadContext->gameDir = VDirOpenZip(args->fname, 0);
276		}
277#endif
278#if ENABLE_LZMA
279		if (!threadContext->gameDir) {
280			threadContext->gameDir = VDirOpen7z(args->fname, 0);
281		}
282#endif
283	}
284	threadContext->fname = args->fname;
285	threadContext->patch = VFileOpen(args->patch, O_RDONLY);
286}
287
288bool GBAThreadStart(struct GBAThread* threadContext) {
289	// TODO: error check
290	threadContext->activeKeys = 0;
291	threadContext->state = THREAD_INITIALIZED;
292	threadContext->sync.videoFrameOn = true;
293	threadContext->sync.videoFrameSkip = 0;
294
295	threadContext->rewindBuffer = 0;
296	int newCapacity = threadContext->rewindBufferCapacity;
297	int newInterval = threadContext->rewindBufferInterval;
298	threadContext->rewindBufferCapacity = 0;
299	threadContext->rewindBufferInterval = 0;
300	GBARewindSettingsChanged(threadContext, newCapacity, newInterval);
301
302	if (!threadContext->fpsTarget) {
303		threadContext->fpsTarget = _defaultFPSTarget;
304	}
305
306	if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
307		threadContext->rom->close(threadContext->rom);
308		threadContext->rom = 0;
309	}
310
311	if (threadContext->gameDir) {
312		threadContext->gameDir->rewind(threadContext->gameDir);
313		struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
314		while (dirent) {
315			struct Patch patchTemp;
316			struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
317			if (!vf) {
318				dirent = threadContext->gameDir->listNext(threadContext->gameDir);
319				continue;
320			}
321			if (!threadContext->rom && GBAIsROM(vf)) {
322				threadContext->rom = vf;
323			} else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
324				threadContext->patch = vf;
325			} else {
326				vf->close(vf);
327			}
328			dirent = threadContext->gameDir->listNext(threadContext->gameDir);
329		}
330
331	}
332
333	if (!threadContext->rom) {
334		threadContext->state = THREAD_SHUTDOWN;
335		return false;
336	}
337
338	threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
339
340	MutexInit(&threadContext->stateMutex);
341	ConditionInit(&threadContext->stateCond);
342
343	MutexInit(&threadContext->sync.videoFrameMutex);
344	ConditionInit(&threadContext->sync.videoFrameAvailableCond);
345	ConditionInit(&threadContext->sync.videoFrameRequiredCond);
346	MutexInit(&threadContext->sync.audioBufferMutex);
347	ConditionInit(&threadContext->sync.audioRequiredCond);
348
349	threadContext->interruptDepth = 0;
350
351#ifndef _WIN32
352	sigset_t signals;
353	sigemptyset(&signals);
354	sigaddset(&signals, SIGINT);
355	sigaddset(&signals, SIGTRAP);
356	pthread_sigmask(SIG_BLOCK, &signals, 0);
357#endif
358
359	MutexLock(&threadContext->stateMutex);
360	ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
361	while (threadContext->state < THREAD_RUNNING) {
362		ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
363	}
364	MutexUnlock(&threadContext->stateMutex);
365
366	return true;
367}
368
369bool GBAThreadHasStarted(struct GBAThread* threadContext) {
370	bool hasStarted;
371	MutexLock(&threadContext->stateMutex);
372	hasStarted = threadContext->state > THREAD_INITIALIZED;
373	MutexUnlock(&threadContext->stateMutex);
374	return hasStarted;
375}
376
377bool GBAThreadHasExited(struct GBAThread* threadContext) {
378	bool hasExited;
379	MutexLock(&threadContext->stateMutex);
380	hasExited = threadContext->state > THREAD_EXITING;
381	MutexUnlock(&threadContext->stateMutex);
382	return hasExited;
383}
384
385bool GBAThreadHasCrashed(struct GBAThread* threadContext) {
386	bool hasExited;
387	MutexLock(&threadContext->stateMutex);
388	hasExited = threadContext->state == THREAD_CRASHED;
389	MutexUnlock(&threadContext->stateMutex);
390	return hasExited;
391}
392
393void GBAThreadEnd(struct GBAThread* threadContext) {
394	MutexLock(&threadContext->stateMutex);
395	if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
396		threadContext->debugger->state = DEBUGGER_EXITING;
397	}
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	if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
499		threadContext->debugger->state = DEBUGGER_EXITING;
500	}
501	ConditionWake(&threadContext->stateCond);
502	_waitUntilNotState(threadContext, THREAD_INTERRUPTING);
503	MutexUnlock(&threadContext->stateMutex);
504}
505
506void GBAThreadContinue(struct GBAThread* threadContext) {
507	MutexLock(&threadContext->stateMutex);
508	--threadContext->interruptDepth;
509	if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
510		threadContext->state = threadContext->savedState;
511		ConditionWake(&threadContext->stateCond);
512	}
513	MutexUnlock(&threadContext->stateMutex);
514}
515
516void GBAThreadPause(struct GBAThread* threadContext) {
517	bool frameOn = true;
518	MutexLock(&threadContext->stateMutex);
519	_waitOnInterrupt(threadContext);
520	if (threadContext->state == THREAD_RUNNING) {
521		_pauseThread(threadContext, false);
522		frameOn = false;
523	}
524	MutexUnlock(&threadContext->stateMutex);
525
526	_changeVideoSync(&threadContext->sync, frameOn);
527}
528
529void GBAThreadUnpause(struct GBAThread* threadContext) {
530	MutexLock(&threadContext->stateMutex);
531	_waitOnInterrupt(threadContext);
532	if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
533		threadContext->state = THREAD_RUNNING;
534		ConditionWake(&threadContext->stateCond);
535	}
536	MutexUnlock(&threadContext->stateMutex);
537
538	_changeVideoSync(&threadContext->sync, true);
539}
540
541bool GBAThreadIsPaused(struct GBAThread* threadContext) {
542	bool isPaused;
543	MutexLock(&threadContext->stateMutex);
544	_waitOnInterrupt(threadContext);
545	isPaused = threadContext->state == THREAD_PAUSED;
546	MutexUnlock(&threadContext->stateMutex);
547	return isPaused;
548}
549
550void GBAThreadTogglePause(struct GBAThread* threadContext) {
551	bool frameOn = true;
552	MutexLock(&threadContext->stateMutex);
553	_waitOnInterrupt(threadContext);
554	if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
555		threadContext->state = THREAD_RUNNING;
556		ConditionWake(&threadContext->stateCond);
557	} else if (threadContext->state == THREAD_RUNNING) {
558		_pauseThread(threadContext, false);
559		frameOn = false;
560	}
561	MutexUnlock(&threadContext->stateMutex);
562
563	_changeVideoSync(&threadContext->sync, frameOn);
564}
565
566void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
567	bool frameOn = true;
568	MutexLock(&threadContext->stateMutex);
569	_waitOnInterrupt(threadContext);
570	if (threadContext->state == THREAD_RUNNING) {
571		_pauseThread(threadContext, true);
572		frameOn = false;
573	}
574	MutexUnlock(&threadContext->stateMutex);
575
576	_changeVideoSync(&threadContext->sync, frameOn);
577}
578
579#ifdef USE_PTHREADS
580struct GBAThread* GBAThreadGetContext(void) {
581	pthread_once(&_contextOnce, _createTLS);
582	return pthread_getspecific(_contextKey);
583}
584#elif _WIN32
585struct GBAThread* GBAThreadGetContext(void) {
586	InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
587	return TlsGetValue(_contextKey);
588}
589#endif
590
591#ifdef USE_PNG
592void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
593	unsigned stride;
594	void* pixels = 0;
595	struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
596	threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
597	png_structp png = PNGWriteOpen(vf);
598	png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
599	PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
600	PNGWriteClose(png, info);
601	vf->close(vf);
602}
603#endif
604
605#else
606struct GBAThread* GBAThreadGetContext(void) {
607	return 0;
608}
609#endif
610
611void GBASyncPostFrame(struct GBASync* sync) {
612	if (!sync) {
613		return;
614	}
615
616	MutexLock(&sync->videoFrameMutex);
617	++sync->videoFramePending;
618	--sync->videoFrameSkip;
619	if (sync->videoFrameSkip < 0) {
620		do {
621			ConditionWake(&sync->videoFrameAvailableCond);
622			if (sync->videoFrameWait) {
623				ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
624			}
625		} while (sync->videoFrameWait && sync->videoFramePending);
626	}
627	MutexUnlock(&sync->videoFrameMutex);
628}
629
630bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
631	if (!sync) {
632		return true;
633	}
634
635	MutexLock(&sync->videoFrameMutex);
636	ConditionWake(&sync->videoFrameRequiredCond);
637	if (!sync->videoFrameOn && !sync->videoFramePending) {
638		return false;
639	}
640	if (sync->videoFrameOn) {
641		ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
642	}
643	sync->videoFramePending = 0;
644	sync->videoFrameSkip = frameskip;
645	return true;
646}
647
648void GBASyncWaitFrameEnd(struct GBASync* sync) {
649	if (!sync) {
650		return;
651	}
652
653	MutexUnlock(&sync->videoFrameMutex);
654}
655
656bool GBASyncDrawingFrame(struct GBASync* sync) {
657	if (!sync) {
658		return true;
659	}
660
661	return sync->videoFrameSkip <= 0;
662}
663
664void GBASyncSuspendDrawing(struct GBASync* sync) {
665	if (!sync) {
666		return;
667	}
668
669	_changeVideoSync(sync, false);
670}
671
672void GBASyncResumeDrawing(struct GBASync* sync) {
673	if (!sync) {
674		return;
675	}
676
677	_changeVideoSync(sync, true);
678}
679
680void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
681	if (!sync) {
682		return;
683	}
684
685	if (sync->audioWait && wait) {
686		// TODO loop properly in event of spurious wakeups
687		ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
688	}
689	MutexUnlock(&sync->audioBufferMutex);
690}
691
692void GBASyncLockAudio(struct GBASync* sync) {
693	if (!sync) {
694		return;
695	}
696
697	MutexLock(&sync->audioBufferMutex);
698}
699
700void GBASyncUnlockAudio(struct GBASync* sync) {
701	if (!sync) {
702		return;
703	}
704
705	MutexUnlock(&sync->audioBufferMutex);
706}
707
708void GBASyncConsumeAudio(struct GBASync* sync) {
709	if (!sync) {
710		return;
711	}
712
713	ConditionWake(&sync->audioRequiredCond);
714	MutexUnlock(&sync->audioBufferMutex);
715}