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
429void GBAThreadInterrupt(struct GBAThread* threadContext) {
430 MutexLock(&threadContext->stateMutex);
431 ++threadContext->interruptDepth;
432 if (threadContext->interruptDepth > 1) {
433 MutexUnlock(&threadContext->stateMutex);
434 return;
435 }
436 threadContext->savedState = threadContext->state;
437 _waitOnInterrupt(threadContext);
438 threadContext->state = THREAD_INTERRUPTING;
439 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
440 threadContext->debugger->state = DEBUGGER_EXITING;
441 }
442 ConditionWake(&threadContext->stateCond);
443 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
444 MutexUnlock(&threadContext->stateMutex);
445}
446
447void GBAThreadContinue(struct GBAThread* threadContext) {
448 MutexLock(&threadContext->stateMutex);
449 --threadContext->interruptDepth;
450 if (threadContext->interruptDepth < 1) {
451 threadContext->state = threadContext->savedState;
452 ConditionWake(&threadContext->stateCond);
453 }
454 MutexUnlock(&threadContext->stateMutex);
455}
456
457void GBAThreadPause(struct GBAThread* threadContext) {
458 bool frameOn = true;
459 MutexLock(&threadContext->stateMutex);
460 _waitOnInterrupt(threadContext);
461 if (threadContext->state == THREAD_RUNNING) {
462 _pauseThread(threadContext, false);
463 frameOn = false;
464 }
465 MutexUnlock(&threadContext->stateMutex);
466
467 _changeVideoSync(&threadContext->sync, frameOn);
468}
469
470void GBAThreadUnpause(struct GBAThread* threadContext) {
471 MutexLock(&threadContext->stateMutex);
472 _waitOnInterrupt(threadContext);
473 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
474 threadContext->state = THREAD_RUNNING;
475 ConditionWake(&threadContext->stateCond);
476 }
477 MutexUnlock(&threadContext->stateMutex);
478
479 _changeVideoSync(&threadContext->sync, true);
480}
481
482bool GBAThreadIsPaused(struct GBAThread* threadContext) {
483 bool isPaused;
484 MutexLock(&threadContext->stateMutex);
485 _waitOnInterrupt(threadContext);
486 isPaused = threadContext->state == THREAD_PAUSED;
487 MutexUnlock(&threadContext->stateMutex);
488 return isPaused;
489}
490
491void GBAThreadTogglePause(struct GBAThread* threadContext) {
492 bool frameOn = true;
493 MutexLock(&threadContext->stateMutex);
494 _waitOnInterrupt(threadContext);
495 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
496 threadContext->state = THREAD_RUNNING;
497 ConditionWake(&threadContext->stateCond);
498 } else if (threadContext->state == THREAD_RUNNING) {
499 _pauseThread(threadContext, false);
500 frameOn = false;
501 }
502 MutexUnlock(&threadContext->stateMutex);
503
504 _changeVideoSync(&threadContext->sync, frameOn);
505}
506
507void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
508 bool frameOn = true;
509 MutexLock(&threadContext->stateMutex);
510 _waitOnInterrupt(threadContext);
511 if (threadContext->state == THREAD_RUNNING) {
512 _pauseThread(threadContext, true);
513 frameOn = false;
514 }
515 MutexUnlock(&threadContext->stateMutex);
516
517 _changeVideoSync(&threadContext->sync, frameOn);
518}
519
520#ifdef USE_PTHREADS
521struct GBAThread* GBAThreadGetContext(void) {
522 pthread_once(&_contextOnce, _createTLS);
523 return pthread_getspecific(_contextKey);
524}
525#else
526struct GBAThread* GBAThreadGetContext(void) {
527 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
528 return TlsGetValue(_contextKey);
529}
530#endif
531
532#ifdef USE_PNG
533void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
534 unsigned stride;
535 void* pixels = 0;
536 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
537 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
538 png_structp png = PNGWriteOpen(vf);
539 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
540 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
541 PNGWriteClose(png, info);
542 vf->close(vf);
543}
544#endif
545
546void GBASyncPostFrame(struct GBASync* sync) {
547 if (!sync) {
548 return;
549 }
550
551 MutexLock(&sync->videoFrameMutex);
552 ++sync->videoFramePending;
553 --sync->videoFrameSkip;
554 if (sync->videoFrameSkip < 0) {
555 do {
556 ConditionWake(&sync->videoFrameAvailableCond);
557 if (sync->videoFrameWait) {
558 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
559 }
560 } while (sync->videoFrameWait && sync->videoFramePending);
561 }
562 MutexUnlock(&sync->videoFrameMutex);
563
564 struct GBAThread* thread = GBAThreadGetContext();
565 if (!thread) {
566 return;
567 }
568
569 if (thread->rewindBuffer) {
570 --thread->rewindBufferNext;
571 if (thread->rewindBufferNext <= 0) {
572 thread->rewindBufferNext = thread->rewindBufferInterval;
573 GBARecordFrame(thread);
574 }
575 }
576 if (thread->stream) {
577 thread->stream->postVideoFrame(thread->stream, thread->renderer);
578 }
579 if (thread->frameCallback) {
580 thread->frameCallback(thread);
581 }
582}
583
584bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
585 if (!sync) {
586 return true;
587 }
588
589 MutexLock(&sync->videoFrameMutex);
590 ConditionWake(&sync->videoFrameRequiredCond);
591 if (!sync->videoFrameOn && !sync->videoFramePending) {
592 return false;
593 }
594 if (sync->videoFrameOn && !sync->videoFramePending) {
595 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
596 }
597 sync->videoFramePending = 0;
598 sync->videoFrameSkip = frameskip;
599 return true;
600}
601
602void GBASyncWaitFrameEnd(struct GBASync* sync) {
603 if (!sync) {
604 return;
605 }
606
607 MutexUnlock(&sync->videoFrameMutex);
608}
609
610bool GBASyncDrawingFrame(struct GBASync* sync) {
611 return sync->videoFrameSkip <= 0;
612}
613
614void GBASyncSuspendDrawing(struct GBASync* sync) {
615 _changeVideoSync(sync, false);
616}
617
618void GBASyncResumeDrawing(struct GBASync* sync) {
619 _changeVideoSync(sync, true);
620}
621
622void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
623 if (sync->audioWait && wait) {
624 // TODO loop properly in event of spurious wakeups
625 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
626 }
627 MutexUnlock(&sync->audioBufferMutex);
628}
629
630void GBASyncLockAudio(struct GBASync* sync) {
631 MutexLock(&sync->audioBufferMutex);
632}
633
634void GBASyncUnlockAudio(struct GBASync* sync) {
635 MutexUnlock(&sync->audioBufferMutex);
636}
637
638void GBASyncConsumeAudio(struct GBASync* sync) {
639 ConditionWake(&sync->audioRequiredCond);
640 MutexUnlock(&sync->audioBufferMutex);
641}