all repos — mgba @ dba275c5705f54501ae6819fb34b9f93a29185c6

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	gba.idleOptimization = threadContext->idleOptimization;
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}