src/gba/gba-thread.c (view raw)
1#include "gba-thread.h"
2
3#include "arm.h"
4#include "gba.h"
5#include "gba-config.h"
6#include "gba-serialize.h"
7
8#include "debugger/debugger.h"
9
10#include "util/patch.h"
11#include "util/png-io.h"
12#include "util/vfs.h"
13
14#include "platform/commandline.h"
15
16#include <signal.h>
17
18static const float _defaultFPSTarget = 60.f;
19
20#ifdef USE_PTHREADS
21static pthread_key_t _contextKey;
22static pthread_once_t _contextOnce = PTHREAD_ONCE_INIT;
23
24static void _createTLS(void) {
25 pthread_key_create(&_contextKey, 0);
26}
27#else
28static DWORD _contextKey;
29static INIT_ONCE _contextOnce = INIT_ONCE_STATIC_INIT;
30
31static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
32 UNUSED(once);
33 UNUSED(param);
34 UNUSED(context);
35 _contextKey = TlsAlloc();
36 return TRUE;
37}
38#endif
39
40static void _changeState(struct GBAThread* threadContext, enum ThreadState newState, bool broadcast) {
41 MutexLock(&threadContext->stateMutex);
42 threadContext->state = newState;
43 if (broadcast) {
44 ConditionWake(&threadContext->stateCond);
45 }
46 MutexUnlock(&threadContext->stateMutex);
47}
48
49static void _waitOnInterrupt(struct GBAThread* threadContext) {
50 while (threadContext->state == THREAD_INTERRUPTED) {
51 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
52 }
53}
54
55static void _waitUntilNotState(struct GBAThread* threadContext, enum ThreadState oldState) {
56 while (threadContext->state == oldState) {
57 MutexUnlock(&threadContext->stateMutex);
58
59 MutexLock(&threadContext->sync.videoFrameMutex);
60 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
61 MutexUnlock(&threadContext->sync.videoFrameMutex);
62
63 MutexLock(&threadContext->sync.audioBufferMutex);
64 ConditionWake(&threadContext->sync.audioRequiredCond);
65 MutexUnlock(&threadContext->sync.audioBufferMutex);
66
67 MutexLock(&threadContext->stateMutex);
68 ConditionWake(&threadContext->stateCond);
69 }
70}
71
72static void _pauseThread(struct GBAThread* threadContext, bool onThread) {
73 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
74 threadContext->debugger->state = DEBUGGER_EXITING;
75 }
76 threadContext->state = THREAD_PAUSING;
77 if (!onThread) {
78 _waitUntilNotState(threadContext, THREAD_PAUSING);
79 }
80}
81
82static void _changeVideoSync(struct GBASync* sync, bool frameOn) {
83 // Make sure the video thread can process events while the GBA thread is paused
84 MutexLock(&sync->videoFrameMutex);
85 if (frameOn != sync->videoFrameOn) {
86 sync->videoFrameOn = frameOn;
87 ConditionWake(&sync->videoFrameAvailableCond);
88 }
89 MutexUnlock(&sync->videoFrameMutex);
90}
91
92static THREAD_ENTRY _GBAThreadRun(void* context) {
93#ifdef USE_PTHREADS
94 pthread_once(&_contextOnce, _createTLS);
95#else
96 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
97#endif
98
99 struct GBA gba;
100 struct ARMCore cpu;
101 struct Patch patch;
102 struct GBAThread* threadContext = context;
103 struct ARMComponent* components[1] = {};
104 int numComponents = 0;
105
106 if (threadContext->debugger) {
107 components[numComponents] = &threadContext->debugger->d;
108 ++numComponents;
109 }
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 threadContext->gba = &gba;
121 gba.sync = &threadContext->sync;
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 if (threadContext->bios) {
142 GBALoadBIOS(&gba, threadContext->bios);
143 }
144
145 if (threadContext->patch && loadPatch(threadContext->patch, &patch)) {
146 GBAApplyPatch(&gba, &patch);
147 }
148 }
149
150 ARMReset(&cpu);
151
152 if (threadContext->debugger) {
153 threadContext->debugger->log = GBADebuggerLogShim;
154 GBAAttachDebugger(&gba, threadContext->debugger);
155 ARMDebuggerEnter(threadContext->debugger, DEBUGGER_ENTER_ATTACHED);
156 }
157
158 GBASIOSetDriverSet(&gba.sio, &threadContext->sioDrivers);
159
160 gba.keySource = &threadContext->activeKeys;
161
162 if (threadContext->startCallback) {
163 threadContext->startCallback(threadContext);
164 }
165
166 _changeState(threadContext, THREAD_RUNNING, true);
167
168 while (threadContext->state < THREAD_EXITING) {
169 if (threadContext->debugger) {
170 struct ARMDebugger* debugger = threadContext->debugger;
171 ARMDebuggerRun(debugger);
172 if (debugger->state == DEBUGGER_SHUTDOWN) {
173 _changeState(threadContext, THREAD_EXITING, false);
174 }
175 } else {
176 while (threadContext->state == THREAD_RUNNING) {
177 ARMRunLoop(&cpu);
178 }
179 }
180
181 int resetScheduled = 0;
182 MutexLock(&threadContext->stateMutex);
183 if (threadContext->state == THREAD_PAUSING) {
184 threadContext->state = THREAD_PAUSED;
185 ConditionWake(&threadContext->stateCond);
186 }
187 if (threadContext->state == THREAD_INTERRUPTING) {
188 threadContext->state = THREAD_INTERRUPTED;
189 ConditionWake(&threadContext->stateCond);
190 }
191 if (threadContext->state == THREAD_RESETING) {
192 threadContext->state = THREAD_RUNNING;
193 resetScheduled = 1;
194 }
195 while (threadContext->state == THREAD_PAUSED) {
196 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
197 }
198 MutexUnlock(&threadContext->stateMutex);
199 if (resetScheduled) {
200 ARMReset(&cpu);
201 }
202 }
203
204 while (threadContext->state != THREAD_SHUTDOWN) {
205 _changeState(threadContext, THREAD_SHUTDOWN, false);
206 }
207
208 if (threadContext->cleanCallback) {
209 threadContext->cleanCallback(threadContext);
210 }
211
212 threadContext->gba = 0;
213 ARMDeinit(&cpu);
214 GBADestroy(&gba);
215
216 threadContext->sync.videoFrameOn = false;
217 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
218 ConditionWake(&threadContext->sync.audioRequiredCond);
219
220 return 0;
221}
222
223void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
224 threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
225 threadContext->frameskip = opts->frameskip;
226 threadContext->logLevel = opts->logLevel;
227 threadContext->rewindBufferCapacity = opts->rewindBufferCapacity;
228 threadContext->rewindBufferInterval = opts->rewindBufferInterval;
229 threadContext->sync.audioWait = opts->audioSync;
230 threadContext->sync.videoFrameWait = opts->videoSync;
231
232 if (opts->fpsTarget) {
233 threadContext->fpsTarget = opts->fpsTarget;
234 }
235
236 if (opts->audioBuffers) {
237 threadContext->audioBuffers = opts->audioBuffers;
238 }
239}
240
241void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* threadContext) {
242 if (args->dirmode) {
243 threadContext->gameDir = VDirOpen(args->fname);
244 threadContext->stateDir = threadContext->gameDir;
245 } else {
246 threadContext->rom = VFileOpen(args->fname, O_RDONLY);
247#if ENABLE_LIBZIP
248 threadContext->gameDir = VDirOpenZip(args->fname, 0);
249#endif
250 }
251 threadContext->fname = args->fname;
252 threadContext->patch = VFileOpen(args->patch, O_RDONLY);
253}
254
255bool GBAThreadStart(struct GBAThread* threadContext) {
256 // TODO: error check
257 threadContext->activeKeys = 0;
258 threadContext->state = THREAD_INITIALIZED;
259 threadContext->sync.videoFrameOn = true;
260 threadContext->sync.videoFrameSkip = 0;
261
262 threadContext->rewindBufferNext = threadContext->rewindBufferInterval;
263 threadContext->rewindBufferSize = 0;
264 if (threadContext->rewindBufferCapacity) {
265 threadContext->rewindBuffer = calloc(threadContext->rewindBufferCapacity, sizeof(void*));
266 } else {
267 threadContext->rewindBuffer = 0;
268 }
269
270 if (!threadContext->fpsTarget) {
271 threadContext->fpsTarget = _defaultFPSTarget;
272 }
273
274 if (threadContext->rom && !GBAIsROM(threadContext->rom)) {
275 threadContext->rom->close(threadContext->rom);
276 threadContext->rom = 0;
277 }
278
279 if (threadContext->gameDir) {
280 threadContext->gameDir->rewind(threadContext->gameDir);
281 struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
282 while (dirent) {
283 struct Patch patchTemp;
284 struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
285 if (!vf) {
286 continue;
287 }
288 if (!threadContext->rom && GBAIsROM(vf)) {
289 threadContext->rom = vf;
290 } else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
291 threadContext->patch = vf;
292 } else {
293 vf->close(vf);
294 }
295 dirent = threadContext->gameDir->listNext(threadContext->gameDir);
296 }
297
298 }
299
300 if (!threadContext->rom) {
301 threadContext->state = THREAD_SHUTDOWN;
302 return false;
303 }
304
305 threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
306
307 MutexInit(&threadContext->stateMutex);
308 ConditionInit(&threadContext->stateCond);
309
310 MutexInit(&threadContext->sync.videoFrameMutex);
311 ConditionInit(&threadContext->sync.videoFrameAvailableCond);
312 ConditionInit(&threadContext->sync.videoFrameRequiredCond);
313 MutexInit(&threadContext->sync.audioBufferMutex);
314 ConditionInit(&threadContext->sync.audioRequiredCond);
315
316 threadContext->interruptDepth = 0;
317
318#ifndef _WIN32
319 sigset_t signals;
320 sigemptyset(&signals);
321 sigaddset(&signals, SIGINT);
322 sigaddset(&signals, SIGTRAP);
323 pthread_sigmask(SIG_BLOCK, &signals, 0);
324#endif
325
326 MutexLock(&threadContext->stateMutex);
327 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
328 while (threadContext->state < THREAD_RUNNING) {
329 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
330 }
331 MutexUnlock(&threadContext->stateMutex);
332
333 return true;
334}
335
336bool GBAThreadHasStarted(struct GBAThread* threadContext) {
337 bool hasStarted;
338 MutexLock(&threadContext->stateMutex);
339 hasStarted = threadContext->state > THREAD_INITIALIZED;
340 MutexUnlock(&threadContext->stateMutex);
341 return hasStarted;
342}
343
344void GBAThreadEnd(struct GBAThread* threadContext) {
345 MutexLock(&threadContext->stateMutex);
346 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
347 threadContext->debugger->state = DEBUGGER_EXITING;
348 }
349 threadContext->state = THREAD_EXITING;
350 ConditionWake(&threadContext->stateCond);
351 MutexUnlock(&threadContext->stateMutex);
352 MutexLock(&threadContext->sync.audioBufferMutex);
353 threadContext->sync.audioWait = 0;
354 ConditionWake(&threadContext->sync.audioRequiredCond);
355 MutexUnlock(&threadContext->sync.audioBufferMutex);
356}
357
358void GBAThreadReset(struct GBAThread* threadContext) {
359 MutexLock(&threadContext->stateMutex);
360 _waitOnInterrupt(threadContext);
361 threadContext->state = THREAD_RESETING;
362 ConditionWake(&threadContext->stateCond);
363 MutexUnlock(&threadContext->stateMutex);
364}
365
366void GBAThreadJoin(struct GBAThread* threadContext) {
367 MutexLock(&threadContext->sync.videoFrameMutex);
368 threadContext->sync.videoFrameWait = 0;
369 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
370 MutexUnlock(&threadContext->sync.videoFrameMutex);
371
372 ThreadJoin(threadContext->thread);
373
374 MutexDeinit(&threadContext->stateMutex);
375 ConditionDeinit(&threadContext->stateCond);
376
377 MutexDeinit(&threadContext->sync.videoFrameMutex);
378 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
379 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
380 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
381 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
382
383 ConditionWake(&threadContext->sync.audioRequiredCond);
384 ConditionDeinit(&threadContext->sync.audioRequiredCond);
385 MutexDeinit(&threadContext->sync.audioBufferMutex);
386
387 int i;
388 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
389 if (threadContext->rewindBuffer[i]) {
390 GBADeallocateState(threadContext->rewindBuffer[i]);
391 }
392 }
393 free(threadContext->rewindBuffer);
394
395 if (threadContext->rom) {
396 threadContext->rom->close(threadContext->rom);
397 threadContext->rom = 0;
398 }
399
400 if (threadContext->save) {
401 threadContext->save->close(threadContext->save);
402 threadContext->save = 0;
403 }
404
405 if (threadContext->bios) {
406 threadContext->bios->close(threadContext->bios);
407 threadContext->bios = 0;
408 }
409
410 if (threadContext->patch) {
411 threadContext->patch->close(threadContext->patch);
412 threadContext->patch = 0;
413 }
414
415 if (threadContext->gameDir) {
416 if (threadContext->stateDir == threadContext->gameDir) {
417 threadContext->stateDir = 0;
418 }
419 threadContext->gameDir->close(threadContext->gameDir);
420 threadContext->gameDir = 0;
421 }
422
423 if (threadContext->stateDir) {
424 threadContext->stateDir->close(threadContext->stateDir);
425 threadContext->stateDir = 0;
426 }
427}
428
429bool GBAThreadIsActive(struct GBAThread* threadContext) {
430 return threadContext->state >= THREAD_RUNNING && threadContext->state < THREAD_EXITING;
431}
432
433void GBAThreadInterrupt(struct GBAThread* threadContext) {
434 MutexLock(&threadContext->stateMutex);
435 ++threadContext->interruptDepth;
436 if (threadContext->interruptDepth > 1 || !GBAThreadIsActive(threadContext)) {
437 MutexUnlock(&threadContext->stateMutex);
438 return;
439 }
440 threadContext->savedState = threadContext->state;
441 _waitOnInterrupt(threadContext);
442 threadContext->state = THREAD_INTERRUPTING;
443 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
444 threadContext->debugger->state = DEBUGGER_EXITING;
445 }
446 ConditionWake(&threadContext->stateCond);
447 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
448 MutexUnlock(&threadContext->stateMutex);
449}
450
451void GBAThreadContinue(struct GBAThread* threadContext) {
452 MutexLock(&threadContext->stateMutex);
453 --threadContext->interruptDepth;
454 if (threadContext->interruptDepth < 1 && GBAThreadIsActive(threadContext)) {
455 threadContext->state = threadContext->savedState;
456 ConditionWake(&threadContext->stateCond);
457 }
458 MutexUnlock(&threadContext->stateMutex);
459}
460
461void GBAThreadPause(struct GBAThread* threadContext) {
462 bool frameOn = true;
463 MutexLock(&threadContext->stateMutex);
464 _waitOnInterrupt(threadContext);
465 if (threadContext->state == THREAD_RUNNING) {
466 _pauseThread(threadContext, false);
467 frameOn = false;
468 }
469 MutexUnlock(&threadContext->stateMutex);
470
471 _changeVideoSync(&threadContext->sync, frameOn);
472}
473
474void GBAThreadUnpause(struct GBAThread* threadContext) {
475 MutexLock(&threadContext->stateMutex);
476 _waitOnInterrupt(threadContext);
477 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
478 threadContext->state = THREAD_RUNNING;
479 ConditionWake(&threadContext->stateCond);
480 }
481 MutexUnlock(&threadContext->stateMutex);
482
483 _changeVideoSync(&threadContext->sync, true);
484}
485
486bool GBAThreadIsPaused(struct GBAThread* threadContext) {
487 bool isPaused;
488 MutexLock(&threadContext->stateMutex);
489 _waitOnInterrupt(threadContext);
490 isPaused = threadContext->state == THREAD_PAUSED;
491 MutexUnlock(&threadContext->stateMutex);
492 return isPaused;
493}
494
495void GBAThreadTogglePause(struct GBAThread* threadContext) {
496 bool frameOn = true;
497 MutexLock(&threadContext->stateMutex);
498 _waitOnInterrupt(threadContext);
499 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
500 threadContext->state = THREAD_RUNNING;
501 ConditionWake(&threadContext->stateCond);
502 } else if (threadContext->state == THREAD_RUNNING) {
503 _pauseThread(threadContext, false);
504 frameOn = false;
505 }
506 MutexUnlock(&threadContext->stateMutex);
507
508 _changeVideoSync(&threadContext->sync, frameOn);
509}
510
511void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
512 bool frameOn = true;
513 MutexLock(&threadContext->stateMutex);
514 _waitOnInterrupt(threadContext);
515 if (threadContext->state == THREAD_RUNNING) {
516 _pauseThread(threadContext, true);
517 frameOn = false;
518 }
519 MutexUnlock(&threadContext->stateMutex);
520
521 _changeVideoSync(&threadContext->sync, frameOn);
522}
523
524#ifdef USE_PTHREADS
525struct GBAThread* GBAThreadGetContext(void) {
526 pthread_once(&_contextOnce, _createTLS);
527 return pthread_getspecific(_contextKey);
528}
529#else
530struct GBAThread* GBAThreadGetContext(void) {
531 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
532 return TlsGetValue(_contextKey);
533}
534#endif
535
536#ifdef USE_PNG
537void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
538 unsigned stride;
539 void* pixels = 0;
540 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
541 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
542 png_structp png = PNGWriteOpen(vf);
543 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
544 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
545 PNGWriteClose(png, info);
546 vf->close(vf);
547}
548#endif
549
550void GBASyncPostFrame(struct GBASync* sync) {
551 if (!sync) {
552 return;
553 }
554
555 MutexLock(&sync->videoFrameMutex);
556 ++sync->videoFramePending;
557 --sync->videoFrameSkip;
558 if (sync->videoFrameSkip < 0) {
559 do {
560 ConditionWake(&sync->videoFrameAvailableCond);
561 if (sync->videoFrameWait) {
562 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
563 }
564 } while (sync->videoFrameWait && sync->videoFramePending);
565 }
566 MutexUnlock(&sync->videoFrameMutex);
567
568 struct GBAThread* thread = GBAThreadGetContext();
569 if (!thread) {
570 return;
571 }
572
573 if (thread->rewindBuffer) {
574 --thread->rewindBufferNext;
575 if (thread->rewindBufferNext <= 0) {
576 thread->rewindBufferNext = thread->rewindBufferInterval;
577 GBARecordFrame(thread);
578 }
579 }
580 if (thread->stream) {
581 thread->stream->postVideoFrame(thread->stream, thread->renderer);
582 }
583 if (thread->frameCallback) {
584 thread->frameCallback(thread);
585 }
586}
587
588bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
589 if (!sync) {
590 return true;
591 }
592
593 MutexLock(&sync->videoFrameMutex);
594 ConditionWake(&sync->videoFrameRequiredCond);
595 if (!sync->videoFrameOn && !sync->videoFramePending) {
596 return false;
597 }
598 if (sync->videoFrameOn && !sync->videoFramePending) {
599 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
600 }
601 sync->videoFramePending = 0;
602 sync->videoFrameSkip = frameskip;
603 return true;
604}
605
606void GBASyncWaitFrameEnd(struct GBASync* sync) {
607 if (!sync) {
608 return;
609 }
610
611 MutexUnlock(&sync->videoFrameMutex);
612}
613
614bool GBASyncDrawingFrame(struct GBASync* sync) {
615 return sync->videoFrameSkip <= 0;
616}
617
618void GBASyncSuspendDrawing(struct GBASync* sync) {
619 _changeVideoSync(sync, false);
620}
621
622void GBASyncResumeDrawing(struct GBASync* sync) {
623 _changeVideoSync(sync, true);
624}
625
626void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
627 if (sync->audioWait && wait) {
628 // TODO loop properly in event of spurious wakeups
629 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
630 }
631 MutexUnlock(&sync->audioBufferMutex);
632}
633
634void GBASyncLockAudio(struct GBASync* sync) {
635 MutexLock(&sync->audioBufferMutex);
636}
637
638void GBASyncUnlockAudio(struct GBASync* sync) {
639 MutexUnlock(&sync->audioBufferMutex);
640}
641
642void GBASyncConsumeAudio(struct GBASync* sync) {
643 ConditionWake(&sync->audioRequiredCond);
644 MutexUnlock(&sync->audioBufferMutex);
645}