all repos — mgba @ 96ac72fbf9ec3d74d9ee250d821916db70d6171e

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