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#ifndef _WIN32
317 sigset_t signals;
318 sigemptyset(&signals);
319 sigaddset(&signals, SIGINT);
320 sigaddset(&signals, SIGTRAP);
321 pthread_sigmask(SIG_BLOCK, &signals, 0);
322#endif
323
324 MutexLock(&threadContext->stateMutex);
325 ThreadCreate(&threadContext->thread, _GBAThreadRun, threadContext);
326 while (threadContext->state < THREAD_RUNNING) {
327 ConditionWait(&threadContext->stateCond, &threadContext->stateMutex);
328 }
329 MutexUnlock(&threadContext->stateMutex);
330
331 return true;
332}
333
334bool GBAThreadHasStarted(struct GBAThread* threadContext) {
335 bool hasStarted;
336 MutexLock(&threadContext->stateMutex);
337 hasStarted = threadContext->state > THREAD_INITIALIZED;
338 MutexUnlock(&threadContext->stateMutex);
339 return hasStarted;
340}
341
342void GBAThreadEnd(struct GBAThread* threadContext) {
343 MutexLock(&threadContext->stateMutex);
344 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
345 threadContext->debugger->state = DEBUGGER_EXITING;
346 }
347 threadContext->state = THREAD_EXITING;
348 ConditionWake(&threadContext->stateCond);
349 MutexUnlock(&threadContext->stateMutex);
350 MutexLock(&threadContext->sync.audioBufferMutex);
351 threadContext->sync.audioWait = 0;
352 ConditionWake(&threadContext->sync.audioRequiredCond);
353 MutexUnlock(&threadContext->sync.audioBufferMutex);
354}
355
356void GBAThreadReset(struct GBAThread* threadContext) {
357 MutexLock(&threadContext->stateMutex);
358 _waitOnInterrupt(threadContext);
359 threadContext->state = THREAD_RESETING;
360 ConditionWake(&threadContext->stateCond);
361 MutexUnlock(&threadContext->stateMutex);
362}
363
364void GBAThreadJoin(struct GBAThread* threadContext) {
365 MutexLock(&threadContext->sync.videoFrameMutex);
366 threadContext->sync.videoFrameWait = 0;
367 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
368 MutexUnlock(&threadContext->sync.videoFrameMutex);
369
370 ThreadJoin(threadContext->thread);
371
372 MutexDeinit(&threadContext->stateMutex);
373 ConditionDeinit(&threadContext->stateCond);
374
375 MutexDeinit(&threadContext->sync.videoFrameMutex);
376 ConditionWake(&threadContext->sync.videoFrameAvailableCond);
377 ConditionDeinit(&threadContext->sync.videoFrameAvailableCond);
378 ConditionWake(&threadContext->sync.videoFrameRequiredCond);
379 ConditionDeinit(&threadContext->sync.videoFrameRequiredCond);
380
381 ConditionWake(&threadContext->sync.audioRequiredCond);
382 ConditionDeinit(&threadContext->sync.audioRequiredCond);
383 MutexDeinit(&threadContext->sync.audioBufferMutex);
384
385 int i;
386 for (i = 0; i < threadContext->rewindBufferCapacity; ++i) {
387 if (threadContext->rewindBuffer[i]) {
388 GBADeallocateState(threadContext->rewindBuffer[i]);
389 }
390 }
391 free(threadContext->rewindBuffer);
392
393 GBAInputMapDeinit(&threadContext->inputMap);
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->savedState = threadContext->state;
432 _waitOnInterrupt(threadContext);
433 threadContext->state = THREAD_INTERRUPTING;
434 if (threadContext->debugger && threadContext->debugger->state == DEBUGGER_RUNNING) {
435 threadContext->debugger->state = DEBUGGER_EXITING;
436 }
437 ConditionWake(&threadContext->stateCond);
438 _waitUntilNotState(threadContext, THREAD_INTERRUPTING);
439 MutexUnlock(&threadContext->stateMutex);
440}
441
442void GBAThreadContinue(struct GBAThread* threadContext) {
443 _changeState(threadContext, threadContext->savedState, 1);
444}
445
446void GBAThreadPause(struct GBAThread* threadContext) {
447 bool frameOn = true;
448 MutexLock(&threadContext->stateMutex);
449 _waitOnInterrupt(threadContext);
450 if (threadContext->state == THREAD_RUNNING) {
451 _pauseThread(threadContext, false);
452 frameOn = false;
453 }
454 MutexUnlock(&threadContext->stateMutex);
455
456 _changeVideoSync(&threadContext->sync, frameOn);
457}
458
459void GBAThreadUnpause(struct GBAThread* threadContext) {
460 MutexLock(&threadContext->stateMutex);
461 _waitOnInterrupt(threadContext);
462 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
463 threadContext->state = THREAD_RUNNING;
464 ConditionWake(&threadContext->stateCond);
465 }
466 MutexUnlock(&threadContext->stateMutex);
467
468 _changeVideoSync(&threadContext->sync, true);
469}
470
471bool GBAThreadIsPaused(struct GBAThread* threadContext) {
472 bool isPaused;
473 MutexLock(&threadContext->stateMutex);
474 _waitOnInterrupt(threadContext);
475 isPaused = threadContext->state == THREAD_PAUSED;
476 MutexUnlock(&threadContext->stateMutex);
477 return isPaused;
478}
479
480void GBAThreadTogglePause(struct GBAThread* threadContext) {
481 bool frameOn = true;
482 MutexLock(&threadContext->stateMutex);
483 _waitOnInterrupt(threadContext);
484 if (threadContext->state == THREAD_PAUSED || threadContext->state == THREAD_PAUSING) {
485 threadContext->state = THREAD_RUNNING;
486 ConditionWake(&threadContext->stateCond);
487 } else if (threadContext->state == THREAD_RUNNING) {
488 _pauseThread(threadContext, false);
489 frameOn = false;
490 }
491 MutexUnlock(&threadContext->stateMutex);
492
493 _changeVideoSync(&threadContext->sync, frameOn);
494}
495
496void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
497 bool frameOn = true;
498 MutexLock(&threadContext->stateMutex);
499 _waitOnInterrupt(threadContext);
500 if (threadContext->state == THREAD_RUNNING) {
501 _pauseThread(threadContext, true);
502 frameOn = false;
503 }
504 MutexUnlock(&threadContext->stateMutex);
505
506 _changeVideoSync(&threadContext->sync, frameOn);
507}
508
509#ifdef USE_PTHREADS
510struct GBAThread* GBAThreadGetContext(void) {
511 pthread_once(&_contextOnce, _createTLS);
512 return pthread_getspecific(_contextKey);
513}
514#else
515struct GBAThread* GBAThreadGetContext(void) {
516 InitOnceExecuteOnce(&_contextOnce, _createTLS, NULL, 0);
517 return TlsGetValue(_contextKey);
518}
519#endif
520
521#ifdef USE_PNG
522void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
523 unsigned stride;
524 void* pixels = 0;
525 struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
526 threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
527 png_structp png = PNGWriteOpen(vf);
528 png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
529 PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
530 PNGWriteClose(png, info);
531 vf->close(vf);
532}
533#endif
534
535void GBASyncPostFrame(struct GBASync* sync) {
536 if (!sync) {
537 return;
538 }
539
540 MutexLock(&sync->videoFrameMutex);
541 ++sync->videoFramePending;
542 --sync->videoFrameSkip;
543 if (sync->videoFrameSkip < 0) {
544 do {
545 ConditionWake(&sync->videoFrameAvailableCond);
546 if (sync->videoFrameWait) {
547 ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex);
548 }
549 } while (sync->videoFrameWait && sync->videoFramePending);
550 }
551 MutexUnlock(&sync->videoFrameMutex);
552
553 struct GBAThread* thread = GBAThreadGetContext();
554 if (!thread) {
555 return;
556 }
557
558 if (thread->rewindBuffer) {
559 --thread->rewindBufferNext;
560 if (thread->rewindBufferNext <= 0) {
561 thread->rewindBufferNext = thread->rewindBufferInterval;
562 GBARecordFrame(thread);
563 }
564 }
565 if (thread->stream) {
566 thread->stream->postVideoFrame(thread->stream, thread->renderer);
567 }
568 if (thread->frameCallback) {
569 thread->frameCallback(thread);
570 }
571}
572
573bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
574 if (!sync) {
575 return true;
576 }
577
578 MutexLock(&sync->videoFrameMutex);
579 ConditionWake(&sync->videoFrameRequiredCond);
580 if (!sync->videoFrameOn && !sync->videoFramePending) {
581 return false;
582 }
583 if (sync->videoFrameOn && !sync->videoFramePending) {
584 ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
585 }
586 sync->videoFramePending = 0;
587 sync->videoFrameSkip = frameskip;
588 return true;
589}
590
591void GBASyncWaitFrameEnd(struct GBASync* sync) {
592 if (!sync) {
593 return;
594 }
595
596 MutexUnlock(&sync->videoFrameMutex);
597}
598
599bool GBASyncDrawingFrame(struct GBASync* sync) {
600 return sync->videoFrameSkip <= 0;
601}
602
603void GBASyncSuspendDrawing(struct GBASync* sync) {
604 _changeVideoSync(sync, false);
605}
606
607void GBASyncResumeDrawing(struct GBASync* sync) {
608 _changeVideoSync(sync, true);
609}
610
611void GBASyncProduceAudio(struct GBASync* sync, bool wait) {
612 if (sync->audioWait && wait) {
613 // TODO loop properly in event of spurious wakeups
614 ConditionWait(&sync->audioRequiredCond, &sync->audioBufferMutex);
615 }
616 MutexUnlock(&sync->audioBufferMutex);
617}
618
619void GBASyncLockAudio(struct GBASync* sync) {
620 MutexLock(&sync->audioBufferMutex);
621}
622
623void GBASyncUnlockAudio(struct GBASync* sync) {
624 MutexUnlock(&sync->audioBufferMutex);
625}
626
627void GBASyncConsumeAudio(struct GBASync* sync) {
628 ConditionWake(&sync->audioRequiredCond);
629 MutexUnlock(&sync->audioBufferMutex);
630}