all repos — mgba @ dcd2e3a64ffda50e97371aa8fc16776d85f8032f

mGBA Game Boy Advance Emulator

src/gba/supervisor/thread.c (view raw)

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